added svcapi ui and camunda code

Signed-off-by: Rohan Patel <rp5811@att.com>
Change-Id: I197b4b40fe3d047a417479214e471ae26d51fb2b
diff --git a/otf-camunda/.gitignore b/otf-camunda/.gitignore
new file mode 100644
index 0000000..7f70bd4
--- /dev/null
+++ b/otf-camunda/.gitignore
@@ -0,0 +1,34 @@
+/target/

+tokens/

+out/

+/otf/

+

+/src/main/resources/bpmn/local/

+/src/main/resources/local/

+src/main/resources/otf_dev.p12

+

+!.mvn/wrapper/maven-wrapper.jar

+

+### STS ###

+.apt_generated

+.classpath

+.factorypath

+.project

+.settings

+.springBeans

+.sts4-cache

+

+### IntelliJ IDEA ###

+.idea

+original.idea

+*.iws

+*.iml

+*.ipr

+

+### NetBeans ###

+/nbproject/private/

+/build/

+/nbbuild/

+/dist/

+/nbdist/

+/.nb-gradle/

diff --git a/otf-camunda/Jenkinsfile b/otf-camunda/Jenkinsfile
new file mode 100644
index 0000000..1548993
--- /dev/null
+++ b/otf-camunda/Jenkinsfile
@@ -0,0 +1,197 @@
+#!/usr/bin/env groovy

+

+

+properties([[$class: 'ParametersDefinitionProperty', parameterDefinitions: [

+        [$class: 'hudson.model.StringParameterDefinition', name: 'PHASE', defaultValue: "BUILD"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'ENV', defaultValue: "dev"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'MECHID', defaultValue: "username"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'OTF_MONGO_DB', defaultValue: "otf_mongo_dev_db"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'OTF_CAMUNDA_DB', defaultValue: "otf_camunda_dev_db"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "org.oran.otf"]

+

+]]])

+

+

+echo "Build branch: ${env.BRANCH_NAME}"

+

+node("docker") {

+    stage 'Checkout'

+    checkout scm

+    PHASES = PHASE.tokenize('_');

+    echo "PHASES : " + PHASES

+    pom = readMavenPom file: 'pom.xml'

+    ARTIFACT_ID = pom.artifactId;

+    VERSION = pom.version;

+    LABEL_VERSION = pom.version.replaceAll("\\.", "-");

+    echo "LabelVerion: " + LABEL_VERSION

+    NAMESPACE = pom.groupId

+    echo "Tiller Namespace: " + TILLER_NAMESPACE

+    DOCKER_REGISTRY = pom.properties['docker.registry']

+

+	if( ENV.equalsIgnoreCase("dev") ){

+	    IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE  + "/" + ARTIFACT_ID + ":" + VERSION

+	}

+	if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr") ){

+	    IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE + ".prod" + "/" + ARTIFACT_ID + ":" + VERSION

+	}

+

+    if( ENV.equalsIgnoreCase("st") ){

+        IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE + ".st" + "/" + ARTIFACT_ID + ":" + VERSION

+    }

+    echo "Artifact: " + IMAGE_NAME

+

+

+	if( ENV.equalsIgnoreCase("dev") ){

+		ROUTER_CONFIG="mysqlRouterConfig-dev.ini"

+	}

+    if( ENV.equalsIgnoreCase("st") ){

+        ROUTER_CONFIG="mysqlRouterConfig-st.ini"

+    }

+	if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr")){

+		ROUTER_CONFIG="mysqlRouterConfig-prod.ini"

+	}

+

+    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}"]) {

+

+        echo "JAVA_HOME=${env.JAVA_HOME}"

+        echo "MAVEN_HOME=${env.MAVEN_HOME}"

+        echo "PATH=${env.PATH}"

+        echo "HELM_HOME=${env.HELM_HOME}"

+

+        wrap([$class: 'ConfigFileBuildWrapper', managedFiles: [

+                [fileId: 'maven-settings.xml', variable: 'MAVEN_SETTINGS'],

+                [fileId: 'maven-settings-security.xml', variable: 'MAVEN_SETTINGS_SECURITY']

+        ]]) {

+

+

+            if (PHASES.contains("BUILD")) {

+                stage 'Compile'

+                sh 'mvn -s $MAVEN_SETTINGS -Dsettings.security=$MAVEN_SETTINGS_SECURITY clean compile'

+

+                stage 'Unit Test'

+                sh 'mvn -s $MAVEN_SETTINGS -Dsettings.security=$MAVEN_SETTINGS_SECURITY test'

+

+                stage 'Package'

+                sh 'mvn -s $MAVEN_SETTINGS -Dsettings.security=$MAVEN_SETTINGS_SECURITY package'

+                //sh 'mvn -DskipTests -Dmaven.test.skip=true -s $MAVEN_SETTINGS package'

+

+                stage 'Verify'

+                sh 'mvn -s $MAVEN_SETTINGS -Dsettings.security=$MAVEN_SETTINGS_SECURITY verify'

+

+                stage 'Publish Artifact'

+

+                withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {

+

+                    echo "Artifact: " + DOCKER_REGISTRY

+

+                    sh """

+						docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD

+						docker build -t $IMAGE_NAME -f target/Dockerfile target

+						docker push $IMAGE_NAME

+					"""

+                }

+

+            }

+            if (PHASES.contains("DEPLOY") || PHASES.contains("UNDEPLOY") || PHASES.contains("UNDEPLOYFORCE")) {

+

+                stage 'Init Helm'

+

+                //check if helm exists if not install

+                if (fileExists('linux-amd64/helm')) {

+                    sh """

+						echo "helm is already installed"

+					"""

+                } else {

+                    //download helm

+                    sh """

+						echo "installing helm"

+						wget  https://storage.googleapis.com/kubernetes-helm/helm-v2.8.2-linux-amd64.tar.gz

+						tar -xf helm-v2.8.2-linux-amd64.tar.gz

+						rm helm-v2.8.2-linux-amd64.tar.gz

+					"""

+                }

+

+                withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) {

+

+                    dir('helm') {

+                        //check if charts are valid, and then perform dry run, if successful then upgrade/install charts

+

+                        if (PHASES.contains("UNDEPLOY")) {

+                            stage 'Undeploy'

+

+                            sh """

+      							helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID

+      						"""

+                        }

+                        if (PHASES.contains("UNDEPLOYFORCE")) {

+                            stage 'Undeploy Force'

+

+                            sh """

+                                

+      							helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID &

+                                chmod 755 forceDelete.sh

+                                ./forceDelete.sh $ARTIFACT_ID

+      						"""

+

+                        }

+

+                        //NOTE Double quotes are used below to access groovy variables like artifact_id and tiller_namespace

+                        if (PHASES.contains("DEPLOY")) {

+                            stage 'Deploy'

+                            withCredentials([

+                            		usernamePassword(credentialsId: OTF_MONGO_DB, usernameVariable: 'USERNAME_MONGO', passwordVariable: 'PASSWORD_MONGO'),

+                            		usernamePassword(credentialsId: OTF_CAMUNDA_DB, usernameVariable: 'USERNAME_CAMUNDA', passwordVariable: 'PASSWORD_CAMUNDA')

+                            	]) {

+

+                                sh """										

+                                                          			

+										echo "Validate Yaml"

+										helm lint $ARTIFACT_ID

+

+										echo "View Helm Templates"

+										helm template $ARTIFACT_ID \

+											--set appName=$ARTIFACT_ID \

+											--set version=$VERSION  \

+											--set image=$IMAGE_NAME \

+											--set namespace=$TILLER_NAMESPACE \

+											--set env=$ENV \

+											--set otf.mongo.username=$USERNAME_MONGO \

+											--set otf.mongo.password=$PASSWORD_MONGO \

+											--set otf.camunda.db.username=$USERNAME_CAMUNDA \

+											--set otf.camunda.db.password=$PASSWORD_CAMUNDA \

+											

+

+										echo "Perform Dry Run Of Install"

+										helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID \

+											--set version=$VERSION  \

+											--set image=$IMAGE_NAME \

+											--set namespace=$TILLER_NAMESPACE \

+											--set env=$ENV \

+											--set otf.mongo.username=$USERNAME_MONGO \

+											--set otf.mongo.password=$PASSWORD_MONGO \

+											--set otf.camunda.db.username=$USERNAME_CAMUNDA \

+											--set otf.camunda.db.password=$PASSWORD_CAMUNDA \

+	

+											

+										echo "Helm Install/Upgrade"

+						    			helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install $ARTIFACT_ID $ARTIFACT_ID \

+											--set version=$VERSION  \

+											--set image=$IMAGE_NAME \

+											--set namespace=$TILLER_NAMESPACE \

+											--set env=$ENV \

+											--set otf.mongo.username=$USERNAME_MONGO \

+											--set otf.mongo.password=$PASSWORD_MONGO \

+											--set otf.camunda.db.username=$USERNAME_CAMUNDA \

+											--set otf.camunda.db.password=$PASSWORD_CAMUNDA \

+

+									"""

+                            }

+                        }

+

+                    }

+                }

+            }

+        }

+    }

+}
\ No newline at end of file
diff --git a/otf-camunda/LICENSE.txt b/otf-camunda/LICENSE.txt
new file mode 100644
index 0000000..695ac56
--- /dev/null
+++ b/otf-camunda/LICENSE.txt
@@ -0,0 +1,28 @@
+Unless otherwise specified, all software contained herein is licensed

+under the Apache License, Version 2.0 (the "Software License");

+you may not use this software except in compliance with the Software

+License. You may obtain a copy of the Software License at

+

+http://www.apache.org/licenses/LICENSE-2.0

+

+Unless required by applicable law or agreed to in writing, software

+distributed under the Software License is distributed on an "AS IS" BASIS,

+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+See the Software License for the specific language governing permissions

+and limitations under the Software License.

+

+

+

+Unless otherwise specified, all documentation contained herein is licensed

+under the Creative Commons License, Attribution 4.0 Intl. (the

+"Documentation License"); you may not use this documentation except in

+compliance with the Documentation License. You may obtain a copy of the

+Documentation License at

+

+https://creativecommons.org/licenses/by/4.0/

+

+Unless required by applicable law or agreed to in writing, documentation

+distributed under the Documentation License is distributed on an "AS IS"

+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+implied. See the Documentation License for the specific language governing

+permissions and limitations under the Documentation License.

diff --git a/otf-camunda/docker/Dockerfile b/otf-camunda/docker/Dockerfile
new file mode 100644
index 0000000..4df636c
--- /dev/null
+++ b/otf-camunda/docker/Dockerfile
@@ -0,0 +1,34 @@
+FROM openjdk:8

+

+ENV ENV=development

+ENV NAMESPACE=namespace

+ENV APP_NAME=otf-camunda

+ENV EXECUTORS_ACTIVE=true

+ENV OTF_MONGO_USERNAME=username

+ENV OTF_MONGO_PASSWORD=password

+ENV OTF_MONGO_HOSTS=localhost:27017

+ENV OTF_MONGO_REPLICASET=mongoOTF

+ENV OTF_MONGO_DATABASE=otf

+ENV OTF_CAMUNDA_DB_URL=localhost:3306/otf-camunda

+ENV OTF_CAMUNDA_DB_USERNAME=username

+ENV OTF_CAMUNDA_DB_PASSWORD=password

+ENV AAF_PERM_TYPE=type

+ENV CADI_HOSTNAME=localhost

+ENV AAF_ID=username

+ENV AAF_MECH_PASSWORD=password

+ENV AAF_PASSWORD=password

+ENV CADI_KEYFILE=/opt/secret/keyfile

+ENV OTF_CERT_PATH=opt/cert/cert.p12

+ENV OTF_CERT_PASS=password

+ENV APP_VERSION=1.0

+ENV PRIVATE_KEY=opt/cert/cert.key

+ENV PRIVATE_KEY_USERNAME=username

+ENV PRIVATE_KEY_PASSPHRASE=password

+

+COPY otf-camunda.jar app.jar

+

+RUN mkdir -p /otf/logs

+

+ADD src src

+

+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
index 0000000..9347939
--- /dev/null
+++ b/otf-camunda/helm/forceDelete.sh
@@ -0,0 +1,11 @@
+#/bin/bash

+podName=$1

+echo $podName

+podInfo=$(kubectl get pods -l app=$1 -o custom-columns=:metadata.name)

+echo $podInfo

+podArray=(`echo ${podInfo}`)

+for var in "${podArray[@]}"

+do

+  echo "Force deleting pod ${var}"

+  kubectl delete pods ${var} --grace-period=0 --force --ignore-not-found=true

+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
index 0000000..9c55445
--- /dev/null
+++ b/otf-camunda/helm/otf-camunda/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1

+appVersion: "1.0"

+description: A Helm chart the OTF TCU camunda engine

+name: otf-camunda

+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
index 0000000..f858b47
--- /dev/null
+++ b/otf-camunda/helm/otf-camunda/mysqlRouterConfig-dev.ini
@@ -0,0 +1,9 @@
+[DEFAULT]

+

+[logger]

+level = INFO

+

+[routing]

+bind_address = 0.0.0.0:3306

+destinations = localhost:3306

+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
index 0000000..6ad94a3
--- /dev/null
+++ b/otf-camunda/helm/otf-camunda/mysqlRouterConfig-prod.ini
@@ -0,0 +1,9 @@
+[DEFAULT]

+

+[logger]

+level = INFO

+

+[routing]

+bind_address = 0.0.0.0:3306

+destinations = 135.49.207.141:3316,135.49.207.140:3316,130.6.37.195:3316

+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
index 0000000..f858b47
--- /dev/null
+++ b/otf-camunda/helm/otf-camunda/mysqlRouterConfig-st.ini
@@ -0,0 +1,9 @@
+[DEFAULT]

+

+[logger]

+level = INFO

+

+[routing]

+bind_address = 0.0.0.0:3306

+destinations = localhost:3306

+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
index 0000000..8c59908
--- /dev/null
+++ b/otf-camunda/helm/otf-camunda/templates/config.yaml
@@ -0,0 +1,13 @@
+apiVersion: v1

+kind: ConfigMap

+metadata:

+  name: {{ .Values.appName }}-config

+data:

+  router_config: |+

+{{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+{{ .Files.Get .Values.otf.camunda.router.config.prod | indent 4}}

+{{ else if eq .Values.env "st"}}

+{{ .Files.Get .Values.otf.camunda.router.config.st | indent 4}}

+{{ else }}

+{{ .Files.Get .Values.otf.camunda.router.config.dev | indent 4}}

+{{ 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
index 0000000..89f751e
--- /dev/null
+++ b/otf-camunda/helm/otf-camunda/templates/deployment.yaml
@@ -0,0 +1,314 @@
+apiVersion: apps/v1beta1

+kind: StatefulSet

+metadata:

+  name: {{ .Values.appName}}-{{ .Values.env }}

+  namespace: {{.Values.namespace}}

+  labels:

+    app: {{ .Values.appName}}

+    version: {{.Values.version}}

+spec:

+  revisionHistoryLimit: 1

+  minReadySeconds: 10

+  strategy:

+  # indicate which strategy we want for rolling update

+    type: RollingUpdate

+    rollingUpdate:

+      maxSurge: 3

+      maxUnavailable: 1

+  replicas: {{ .Values.replicas}}

+  selector:

+    matchLabels:

+      app: {{ .Values.appName}}

+      version: {{.Values.version}}

+  template:

+    metadata:

+      labels:

+        app: {{ .Values.appName}}

+        version: {{.Values.version}}

+    spec:

+      serviceAccount: default

+      volumes:

+      - name: {{ .Values.appName}}-aaf-volume

+        secret:

+          secretName: {{.Values.sharedSecret}}

+      - name: {{ .Values.appName}}-keyfile-volume

+        secret:

+          secretName: {{.Values.sharedSecret}}

+          optional: true

+          items:

+          - key: cadi_keyfile

+            path: keyfile

+      - name: {{ .Values.appName}}-cert-volume

+        secret:

+          secretName: {{.Values.sharedCert}}

+          optional: true

+          items:

+          - key: PKCS12_CERT

+            {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+            path: {{ .Values.cert.prod.name | quote }}

+            {{ else if eq  .Values.env "st" }}

+            path: {{ .Values.cert.st.name | quote }}

+            {{ else }}

+            path: {{ .Values.cert.dev.name | quote }}

+            {{ end }}

+          - key: private_key

+            path: {{ .Values.Secret.privateKey.name }}

+      - name: {{.Values.appName}}-config-volume

+        configMap:

+          name: {{.Values.appName}}-config

+          items:

+          - key: router_config

+            path: config.ini

+      {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}}

+      {{else}}

+      - name: logging-pvc

+        persistentVolumeClaim:

+          {{if eq .Values.env "prod"}}

+          claimName: {{ .Values.pvc.prod | quote }}

+          {{ else }}

+          claimName: {{ .Values.pvc.dev | quote }}

+          {{ end }}

+      {{end}}

+      containers:

+      - name: mysql-router

+        image: {{ .Values.otf.camunda.router.image }}

+        imagePullPolicy: Always

+        ports:

+        - name: http

+          containerPort: {{ .Values.otf.camunda.router.port }}

+          protocol: TCP

+        {{ if eq .Values.env "st"}}

+        resources:

+          limits:

+            memory: "1Gi"

+            cpu: "500m"

+          requests:

+            memory: "512Mi"

+            cpu: "100m"

+        {{else}}

+        resources:

+          limits:

+            memory: "4Gi"

+            cpu: "2"

+          requests:

+            memory: "2Gi"

+            cpu: "1"

+        {{end}}

+        args: ["--config=/opt/config/config.ini"]

+        lifecycle:

+          preStop:

+            exec:

+              command: ["/bin/sh", "-c", {{ "sleep 0" | replace "0" (.Values.terminationGracePeriodSeconds | toString) | quote}} ]

+        volumeMounts:

+        - name: {{.Values.appName}}-config-volume

+          mountPath: /opt/config

+      - name: {{ .Values.appName}}

+        image: {{ .Values.image}}

+        imagePullPolicy: Always

+        ports:

+        - name: http

+          containerPort: {{ .Values.otf.camunda.tcu.port }}

+          nodePort: {{.Values.nodePort}}

+          protocol: TCP

+        {{ if eq .Values.env "st"}}

+        resources:

+          limits:

+            memory: "6Gi"

+            cpu: "2.8"

+          requests:

+            memory: "2Gi"

+            cpu: "1.5"

+        {{else}}

+        resources:

+          limits:

+            memory: "10Gi"

+            cpu: "6"

+          requests:

+            memory: "4Gi"

+            cpu: "2"

+        {{end}}

+        env:

+        - name: ENV

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: "production"

+          {{ else if eq .Values.env "st" }}

+          value: "system_test"

+          {{ else }}

+          value: "development"

+          {{ end }}

+        - name: NAMESPACE

+          value: {{.Values.namespace}}

+        - name: APP_NAME

+          value: {{ .Values.appName}}

+        - name: EXECUTORS_ACTIVE

+          {{if eq .Values.env "prod"}}

+          value: {{ .Values.otf.camunda.executors_active.prod | quote }}

+          {{else if eq .Values.env "prod-dr"}}

+          value: {{ .Values.otf.camunda.executors_active.prod_dr | quote }}

+          {{else if  eq .Values.env "st"}}

+          value: {{ .Values.otf.camunda.executors_active.st | quote }}

+          {{ else }}

+          value: {{ .Values.otf.camunda.executors_active.dev | quote }}

+          {{ end }}

+        - name: OTF_MONGO_USERNAME

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.appName}}

+              key: mongo_username

+              optional: true

+        - name: OTF_MONGO_PASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.appName}}

+              key: mongo_password

+              optional: true

+        - name: OTF_MONGO_HOSTS

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.otf.mongo.prod.host | quote }}

+          {{ else if eq  .Values.env "st" }}

+          value: {{ .Values.otf.mongo.st.host | quote }}

+          {{ else }}

+          value: {{.Values.otf.mongo.dev.host | quote }}

+          {{ end }}

+        - name: OTF_MONGO_REPLICASET

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.otf.mongo.prod.replicaSet | quote }}

+          {{ else if eq .Values.env "st"}}

+          value: {{ .Values.otf.mongo.st.replicaSet | quote }}

+          {{ else }}

+          value: {{ .Values.otf.mongo.dev.replicaSet | quote }}

+          {{ end }}

+        - name: OTF_MONGO_DATABASE

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.otf.mongo.prod.database | quote }}

+          {{else if  eq .Values.env "st"}}

+          value: {{ .Values.otf.mongo.st.database | quote }}

+          {{ else }}

+          value: {{ .Values.otf.mongo.dev.database | quote }}

+          {{ end }}

+        - name: OTF_CAMUNDA_DB_URL

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.otf.camunda.db.prod.url}}

+          {{else if  eq .Values.env "st"}}

+          value: {{ .Values.otf.camunda.db.st.url}}

+          {{ else }}

+          value: {{ .Values.otf.camunda.db.dev.url}}

+          {{ end }}

+        - name: OTF_CAMUNDA_DB_USERNAME

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.appName}}

+              key: camunda_db_username

+              optional: true

+        - name: OTF_CAMUNDA_DB_PASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.appName}}

+              key: camunda_db_password

+              optional: true

+        - name: AAF_PERM_TYPE

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.aafPermType.prod | quote }}

+          {{ else if  eq .Values.env "st"}}

+          value: {{ .Values.aafPermType.st | quote }}

+          {{ else }}

+          value: {{ .Values.aafPermType.dev | quote }}

+          {{ end }}

+        - name: CADI_HOSTNAME

+          {{if eq .Values.env "prod"}}

+          value: {{ .Values.cadiHostname.prod | quote }}

+          {{else if eq .Values.env "prod-dr"}}

+          value: {{ .Values.cadiHostname.prod_dr | quote }}

+          {{else if  eq .Values.env "st"}}

+          value: {{ .Values.cadiHostname.st | quote }}

+          {{ else }}

+          value: {{ .Values.cadiHostname.dev | quote }}

+          {{ end }}

+        - name: AAF_ID

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_id

+              optional: true

+        - name: AAF_MECH_PASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_mech_password

+              optional: true

+        - name: AAF_PASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_password

+              optional: true

+        - name: CADI_KEYFILE

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: keyfile_secret_path

+              optional: true

+        - name: OTF_CERT_PATH

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.cert.prod.path | quote }}

+          {{ else if eq  .Values.env "st" }}

+          value: {{ .Values.cert.st.path | quote }}

+          {{ else }}

+          value: {{ .Values.cert.dev.path | quote }}

+          {{ end }}

+        - name: OTF_CERT_PASS

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedCert}}

+              key: PKCS12_KEY

+              optional: true

+        - name: APP_VERSION

+          value: {{.Values.version}}

+        - name: PRIVATE_KEY

+          value: {{ .Values.Secret.privateKey.path }}

+        - name: PRIVATE_KEY_USERNAME

+          valueFrom:

+            secretKeyRef:

+              name: {{.Values.sharedCert}}

+              key: private_key_username

+              optional: true

+        - name: PRIVATE_KEY_PASSPHRASE

+          valueFrom:

+            secretKeyRef:

+              name: {{.Values.sharedCert}}

+              key: private_key_passphrase

+              optional: true

+        volumeMounts:

+        - name: {{.Values.appName}}-keyfile-volume

+          mountPath: /opt/secret

+        - name: {{.Values.appName}}-cert-volume

+          mountPath: /opt/cert

+        {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}}

+        {{else}}

+        - name: logging-pvc

+          mountPath: "/otf/logs"

+        {{end}}   

+        livenessProbe:

+          httpGet:

+            path: /otf/health/v1

+            port: http

+            scheme: HTTPS

+            httpHeaders:

+            - name: X-Custom-Header

+              value: Alive

+          initialDelaySeconds: 30

+          timeoutSeconds: 30

+          periodSeconds: 30

+        readinessProbe:

+          httpGet:

+            path: /otf/health/v1

+            port: http

+            scheme: HTTPS

+            httpHeaders:

+            - name: X-Custom-Header

+              value: Ready

+          initialDelaySeconds: 30

+          timeoutSeconds: 30

+          periodSeconds: 30

+      restartPolicy: Always

+      terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds}}

diff --git a/otf-camunda/helm/otf-camunda/templates/secret.yaml b/otf-camunda/helm/otf-camunda/templates/secret.yaml
new file mode 100644
index 0000000..52438d2
--- /dev/null
+++ b/otf-camunda/helm/otf-camunda/templates/secret.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1

+kind: Secret

+metadata:

+  name: {{ .Values.appName}}

+type: Opaque

+data:

+  mongo_username: {{ .Values.otf.mongo.username | b64enc}}

+  mongo_password: {{ .Values.otf.mongo.password | b64enc}}

+  camunda_db_username: {{ .Values.otf.camunda.db.username | b64enc}}

+  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
index 0000000..ae5f832
--- /dev/null
+++ b/otf-camunda/helm/otf-camunda/templates/service.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1

+kind: Service

+metadata:

+  name: {{ .Values.appName }}

+  namespace: {{ .Values.namespace}}

+  labels:

+    app: {{ .Values.appName }}

+    version: {{ .Values.version}}

+spec:

+  type: NodePort

+  ports:

+  - name: http

+    port: {{ .Values.otf.camunda.tcu.port }}

+    protocol: TCP

+    nodePort: {{ .Values.nodePort}}

+  selector:

+    app: {{ .Values.appName }}

+    version: {{ .Values.version}}

diff --git a/otf-camunda/helm/otf-camunda/values.yaml b/otf-camunda/helm/otf-camunda/values.yaml
new file mode 100644
index 0000000..cbd86f0
--- /dev/null
+++ b/otf-camunda/helm/otf-camunda/values.yaml
@@ -0,0 +1,86 @@
+appName: otf-camunda

+version: 0.0.1-SNAPSHOT

+image: otf-camunda:0.0.1-SNAPSHOT

+namespace: org.oran.otf

+nodePort: 31313

+replicas: 2

+terminationGracePeriodSeconds: 360

+env: dev

+

+# Environment variables for the service api.

+otf:

+  mongo:

+    dev:

+      host: localhost:27017,localhost:27017,localhost:27017

+      replicaSet: mongoOTF

+      database: otf

+    st:

+      host: localhost:27017,localhost:27017,localhost:27017

+      replicaSet: mongoOTF

+      database: otf_st

+    prod:

+      host: localhost:18720,localhost:18720,localhost:18720

+      replicaSet: otf-rs-prod2

+      database: otf

+    username: "test"

+    password: "test"

+  camunda:

+    executors_active:

+      dev: true

+      st: true

+      prod: false

+      prod_dr: true

+    tcu:

+      port: 8443

+    db:

+      dev:

+        url: localhost:3306/otf-camunda

+      st:

+        url: localhost:3306/otf_st-camunda

+      prod:

+        url: localhost:3306/otf-camunda

+      username: username

+      password: password

+    router:

+      config:

+        dev: mysqlRouterConfig-dev.ini

+        st: mysqlRouterConfig-st.ini

+        prod: mysqlRouterConfig-prod.ini

+      image: mysql/mysql-router

+      port: 3306

+# permission type for aaf

+aafPermType:

+  dev: org.oran.otf.dev.camunda

+  st: org.oran.otf.st.camunda   

+  prod: org.oran.otf.prod.camunda

+

+cadiHostname:

+  dev: localhost

+  st: localhost

+  prod: localhost

+  prod_dr: localhost

+

+  

+# Secret related information.

+sharedSecret: otf-aaf-credential-generator

+sharedCert: otf-cert-secret-builder

+cert:

+  dev: 

+    name: otf_dev.p12

+    path: opt/cert/otf_dev.p12

+  st: 

+    name: otf_st.p12

+    path: opt/cert/otf_st.p12

+  prod: 

+    name: otf_prod.p12

+    path: opt/cert/otf_prod.p12

+

+Secret:

+  privateKey:

+    name: key.key

+    path: opt/cert/key.key

+

+pvc:

+  dev: org-oran-otf-dev-logs-pv

+  prod: org-oran-otf-prod-logs-pv

+

diff --git a/otf-camunda/mvnw b/otf-camunda/mvnw
new file mode 100644
index 0000000..7778d42
--- /dev/null
+++ b/otf-camunda/mvnw
@@ -0,0 +1,225 @@
+#!/bin/sh

+# ----------------------------------------------------------------------------

+# Licensed to the Apache Software Foundation (ASF) under one

+# or more contributor license agreements.  See the NOTICE file

+# distributed with this work for additional information

+# regarding copyright ownership.  The ASF licenses this file

+# to you under the Apache License, Version 2.0 (the

+# "License"); you may not use this file except in compliance

+# with the License.  You may obtain a copy of the License at

+#

+#    http://www.apache.org/licenses/LICENSE-2.0

+#

+# Unless required by applicable law or agreed to in writing,

+# software distributed under the License is distributed on an

+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+# KIND, either express or implied.  See the License for the

+# specific language governing permissions and limitations

+# under the License.

+# ----------------------------------------------------------------------------

+

+# ----------------------------------------------------------------------------

+# Maven2 Start Up Batch script

+#

+# Required ENV vars:

+# ------------------

+#   JAVA_HOME - location of a JDK home dir

+#

+# Optional ENV vars

+# -----------------

+#   M2_HOME - location of maven2's installed home dir

+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven

+#     e.g. to debug Maven itself, use

+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000

+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files

+# ----------------------------------------------------------------------------

+

+if [ -z "$MAVEN_SKIP_RC" ] ; then

+

+  if [ -f /etc/mavenrc ] ; then

+    . /etc/mavenrc

+  fi

+

+  if [ -f "$HOME/.mavenrc" ] ; then

+    . "$HOME/.mavenrc"

+  fi

+

+fi

+

+# OS specific support.  $var _must_ be set to either true or false.

+cygwin=false;

+darwin=false;

+mingw=false

+case "`uname`" in

+  CYGWIN*) cygwin=true ;;

+  MINGW*) mingw=true;;

+  Darwin*) darwin=true

+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home

+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html

+    if [ -z "$JAVA_HOME" ]; then

+      if [ -x "/usr/libexec/java_home" ]; then

+        export JAVA_HOME="`/usr/libexec/java_home`"

+      else

+        export JAVA_HOME="/Library/Java/Home"

+      fi

+    fi

+    ;;

+esac

+

+if [ -z "$JAVA_HOME" ] ; then

+  if [ -r /etc/gentoo-release ] ; then

+    JAVA_HOME=`java-config --jre-home`

+  fi

+fi

+

+if [ -z "$M2_HOME" ] ; then

+  ## resolve links - $0 may be a link to maven's home

+  PRG="$0"

+

+  # need this for relative symlinks

+  while [ -h "$PRG" ] ; do

+    ls=`ls -ld "$PRG"`

+    link=`expr "$ls" : '.*-> \(.*\)$'`

+    if expr "$link" : '/.*' > /dev/null; then

+      PRG="$link"

+    else

+      PRG="`dirname "$PRG"`/$link"

+    fi

+  done

+

+  saveddir=`pwd`

+

+  M2_HOME=`dirname "$PRG"`/..

+

+  # make it fully qualified

+  M2_HOME=`cd "$M2_HOME" && pwd`

+

+  cd "$saveddir"

+  # echo Using m2 at $M2_HOME

+fi

+

+# For Cygwin, ensure paths are in UNIX format before anything is touched

+if $cygwin ; then

+  [ -n "$M2_HOME" ] &&

+    M2_HOME=`cygpath --unix "$M2_HOME"`

+  [ -n "$JAVA_HOME" ] &&

+    JAVA_HOME=`cygpath --unix "$JAVA_HOME"`

+  [ -n "$CLASSPATH" ] &&

+    CLASSPATH=`cygpath --path --unix "$CLASSPATH"`

+fi

+

+# For Migwn, ensure paths are in UNIX format before anything is touched

+if $mingw ; then

+  [ -n "$M2_HOME" ] &&

+    M2_HOME="`(cd "$M2_HOME"; pwd)`"

+  [ -n "$JAVA_HOME" ] &&

+    JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"

+  # TODO classpath?

+fi

+

+if [ -z "$JAVA_HOME" ]; then

+  javaExecutable="`which javac`"

+  if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then

+    # readlink(1) is not available as standard on Solaris 10.

+    readLink=`which readlink`

+    if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then

+      if $darwin ; then

+        javaHome="`dirname \"$javaExecutable\"`"

+        javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"

+      else

+        javaExecutable="`readlink -f \"$javaExecutable\"`"

+      fi

+      javaHome="`dirname \"$javaExecutable\"`"

+      javaHome=`expr "$javaHome" : '\(.*\)/bin'`

+      JAVA_HOME="$javaHome"

+      export JAVA_HOME

+    fi

+  fi

+fi

+

+if [ -z "$JAVACMD" ] ; then

+  if [ -n "$JAVA_HOME"  ] ; then

+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then

+      # IBM's JDK on AIX uses strange locations for the executables

+      JAVACMD="$JAVA_HOME/jre/sh/java"

+    else

+      JAVACMD="$JAVA_HOME/bin/java"

+    fi

+  else

+    JAVACMD="`which java`"

+  fi

+fi

+

+if [ ! -x "$JAVACMD" ] ; then

+  echo "Error: JAVA_HOME is not defined correctly." >&2

+  echo "  We cannot execute $JAVACMD" >&2

+  exit 1

+fi

+

+if [ -z "$JAVA_HOME" ] ; then

+  echo "Warning: JAVA_HOME environment variable is not set."

+fi

+

+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher

+

+# traverses directory structure from process work directory to filesystem root

+# first directory with .mvn subdirectory is considered project base directory

+find_maven_basedir() {

+

+  if [ -z "$1" ]

+  then

+    echo "Path not specified to find_maven_basedir"

+    return 1

+  fi

+

+  basedir="$1"

+  wdir="$1"

+  while [ "$wdir" != '/' ] ; do

+    if [ -d "$wdir"/.mvn ] ; then

+      basedir=$wdir

+      break

+    fi

+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)

+    if [ -d "${wdir}" ]; then

+      wdir=`cd "$wdir/.."; pwd`

+    fi

+    # end of workaround

+  done

+  echo "${basedir}"

+}

+

+# concatenates all lines of a file

+concat_lines() {

+  if [ -f "$1" ]; then

+    echo "$(tr -s '\n' ' ' < "$1")"

+  fi

+}

+

+BASE_DIR=`find_maven_basedir "$(pwd)"`

+if [ -z "$BASE_DIR" ]; then

+  exit 1;

+fi

+

+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}

+echo $MAVEN_PROJECTBASEDIR

+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"

+

+# For Cygwin, switch paths to Windows format before running java

+if $cygwin; then

+  [ -n "$M2_HOME" ] &&

+    M2_HOME=`cygpath --path --windows "$M2_HOME"`

+  [ -n "$JAVA_HOME" ] &&

+    JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`

+  [ -n "$CLASSPATH" ] &&

+    CLASSPATH=`cygpath --path --windows "$CLASSPATH"`

+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&

+    MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`

+fi

+

+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain

+

+exec "$JAVACMD" \

+  $MAVEN_OPTS \

+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \

+  "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \

+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

diff --git a/otf-camunda/mvnw.cmd b/otf-camunda/mvnw.cmd
new file mode 100644
index 0000000..48c810e
--- /dev/null
+++ b/otf-camunda/mvnw.cmd
@@ -0,0 +1,143 @@
+@REM ----------------------------------------------------------------------------

+@REM Licensed to the Apache Software Foundation (ASF) under one

+@REM or more contributor license agreements.  See the NOTICE file

+@REM distributed with this work for additional information

+@REM regarding copyright ownership.  The ASF licenses this file

+@REM to you under the Apache License, Version 2.0 (the

+@REM "License"); you may not use this file except in compliance

+@REM with the License.  You may obtain a copy of the License at

+@REM

+@REM    http://www.apache.org/licenses/LICENSE-2.0

+@REM

+@REM Unless required by applicable law or agreed to in writing,

+@REM software distributed under the License is distributed on an

+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY

+@REM KIND, either express or implied.  See the License for the

+@REM specific language governing permissions and limitations

+@REM under the License.

+@REM ----------------------------------------------------------------------------

+

+@REM ----------------------------------------------------------------------------

+@REM Maven2 Start Up Batch script

+@REM

+@REM Required ENV vars:

+@REM JAVA_HOME - location of a JDK home dir

+@REM

+@REM Optional ENV vars

+@REM M2_HOME - location of maven2's installed home dir

+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands

+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending

+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven

+@REM     e.g. to debug Maven itself, use

+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000

+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files

+@REM ----------------------------------------------------------------------------

+

+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'

+@echo off

+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'

+@if "%MAVEN_BATCH_ECHO%" == "on"  echo %MAVEN_BATCH_ECHO%

+

+@REM set %HOME% to equivalent of $HOME

+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")

+

+@REM Execute a user defined script before this one

+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre

+@REM check for pre script, once with legacy .bat ending and once with .cmd ending

+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"

+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"

+:skipRcPre

+

+@setlocal

+

+set ERROR_CODE=0

+

+@REM To isolate internal variables from possible post scripts, we use another setlocal

+@setlocal

+

+@REM ==== START VALIDATION ====

+if not "%JAVA_HOME%" == "" goto OkJHome

+

+echo.

+echo Error: JAVA_HOME not found in your environment. >&2

+echo Please set the JAVA_HOME variable in your environment to match the >&2

+echo location of your Java installation. >&2

+echo.

+goto error

+

+:OkJHome

+if exist "%JAVA_HOME%\bin\java.exe" goto init

+

+echo.

+echo Error: JAVA_HOME is set to an invalid directory. >&2

+echo JAVA_HOME = "%JAVA_HOME%" >&2

+echo Please set the JAVA_HOME variable in your environment to match the >&2

+echo location of your Java installation. >&2

+echo.

+goto error

+

+@REM ==== END VALIDATION ====

+

+:init

+

+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".

+@REM Fallback to current working directory if not found.

+

+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%

+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir

+

+set EXEC_DIR=%CD%

+set WDIR=%EXEC_DIR%

+:findBaseDir

+IF EXIST "%WDIR%"\.mvn goto baseDirFound

+cd ..

+IF "%WDIR%"=="%CD%" goto baseDirNotFound

+set WDIR=%CD%

+goto findBaseDir

+

+:baseDirFound

+set MAVEN_PROJECTBASEDIR=%WDIR%

+cd "%EXEC_DIR%"

+goto endDetectBaseDir

+

+:baseDirNotFound

+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%

+cd "%EXEC_DIR%"

+

+:endDetectBaseDir

+

+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig

+

+@setlocal EnableExtensions EnableDelayedExpansion

+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a

+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%

+

+:endReadAdditionalConfig

+

+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"

+

+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"

+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain

+

+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*

+if ERRORLEVEL 1 goto error

+goto end

+

+:error

+set ERROR_CODE=1

+

+:end

+@endlocal & set ERROR_CODE=%ERROR_CODE%

+

+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost

+@REM check for post script, once with legacy .bat ending and once with .cmd ending

+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"

+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"

+:skipRcPost

+

+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'

+if "%MAVEN_BATCH_PAUSE%" == "on" pause

+

+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%

+

+exit /B %ERROR_CODE%

diff --git a/otf-camunda/pom.xml b/otf-camunda/pom.xml
new file mode 100644
index 0000000..4fa0994
--- /dev/null
+++ b/otf-camunda/pom.xml
@@ -0,0 +1,413 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<project xmlns="http://maven.apache.org/POM/4.0.0"

+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

+  <modelVersion>4.0.0</modelVersion>

+

+  <groupId>org.oran.otf</groupId>

+  <artifactId>otf-camunda</artifactId>

+  <version>Camille.1.1</version>

+  <packaging>jar</packaging>

+  <dependencies>

+

+    <dependency>

+      <groupId>com.github.tomakehurst</groupId>

+      <artifactId>wiremock-jre8</artifactId>

+      <version>2.24.0</version>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>org.mockito</groupId>

+      <artifactId>mockito-core</artifactId>

+      <version>2.15.0</version>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>org.mockito</groupId>

+      <artifactId>mockito-inline</artifactId>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>io.rest-assured</groupId>

+      <artifactId>rest-assured</artifactId>

+      <version>4.0.0</version>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>io.rest-assured</groupId>

+      <artifactId>rest-assured-all</artifactId>

+      <version>4.0.0</version>

+      <scope>test</scope>

+    </dependency>

+

+

+

+    <!-- Camunda BPM dependencies -->

+    <dependency>

+      <groupId>org.camunda.bpm</groupId>

+      <artifactId>camunda-engine</artifactId>

+      <version>${camunda.bpm.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.camunda.bpm</groupId>

+      <artifactId>camunda-engine-plugin-spin</artifactId>

+      <version>${camunda.bpm.base.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.camunda.bpm</groupId>

+      <artifactId>camunda-engine-plugin-connect</artifactId>

+      <version>${camunda.bpm.base.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.camunda.connect</groupId>

+      <artifactId>camunda-connect-connectors-all</artifactId>

+    </dependency>

+    <dependency>

+      <groupId>org.camunda.bpm.springboot</groupId>

+      <artifactId>camunda-bpm-spring-boot-starter</artifactId>

+      <version>${camunda.springboot.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.camunda.bpm.springboot</groupId>

+      <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>

+      <version>${camunda.springboot.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.camunda.bpm.springboot</groupId>

+      <artifactId>camunda-bpm-spring-boot-starter-webapp-ee</artifactId>

+      <version>${camunda.springboot.version}</version>

+    </dependency>

+    <dependency>

+      <artifactId>camunda-external-task-client</artifactId>

+      <groupId>org.camunda.bpm</groupId>

+      <version>${camunda.bpm.external-task-client.version}</version>

+    </dependency>

+    <!-- End Camunda BPM dependencies -->

+    <!-- Begin Camunda BPM extension dependencies -->

+    <dependency>

+      <groupId>org.camunda.bpm.extension.mockito</groupId>

+      <artifactId>camunda-bpm-mockito</artifactId>

+      <version>${camunda.mockito.version}</version>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>org.camunda.bpm.extension.reactor</groupId>

+      <artifactId>camunda-bpm-reactor-core</artifactId>

+      <version>${camunda.bpm.reactor.version}</version>

+    </dependency>

+    <dependency>

+      <artifactId>camunda-bpm-reactor-spring</artifactId>

+      <groupId>org.camunda.bpm.extension.reactor</groupId>

+      <version>${camunda.bpm.reactor.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.camunda.bpm.extension</groupId>

+      <artifactId>camunda-bpm-assert</artifactId>

+      <version>${camunda.bpm.assert.version}</version>

+    </dependency>

+<!--g-->

+    <!-- End Camunda BPM extension dependencies -->

+    <!-- Begin Camunda BPM Spin dependencies -->

+    <dependency>

+      <groupId>org.camunda.spin</groupId>

+      <artifactId>camunda-spin-core</artifactId>

+      <version>${camunda.spin.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.camunda.spin</groupId>

+      <artifactId>camunda-spin-dataformat-all</artifactId>

+      <version>${camunda.spin.version}</version>

+    </dependency>

+    <!-- End Camunda BPM Spin dependencies -->

+    <!-- Begin Spring Boot dependencies -->

+    <dependency>

+      <groupId>org.springframework.boot</groupId>

+      <artifactId>spring-boot-starter-actuator</artifactId>

+      <version>${springboot.version}</version>

+    </dependency>

+    <dependency>

+      <artifactId>spring-boot-starter-amqp</artifactId>

+      <groupId>org.springframework.boot</groupId>

+      <version>${springboot.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.springframework.boot</groupId>

+      <artifactId>spring-boot-starter-jersey</artifactId>

+      <version>${springboot.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.springframework.boot</groupId>

+      <artifactId>spring-boot-starter-data-mongodb</artifactId>

+      <version>${springboot.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.springframework.boot</groupId>

+      <artifactId>spring-boot-starter-test</artifactId>

+      <scope>test</scope>

+      <version>${springboot.version}</version>

+    </dependency>

+    <!--    <dependency>-->

+    <!--      <groupId>org.springframework.boot</groupId>-->

+    <!--      <artifactId>spring-boot-starter-web</artifactId>-->

+    <!--      <version>${springboot.version}</version>-->

+    <!--    </dependency>-->

+    <dependency>

+      <groupId>org.springframework.boot</groupId>

+      <artifactId>spring-boot-configuration-processor</artifactId>

+      <version>${springboot.version}</version>

+      <optional>true</optional>

+    </dependency>

+    <dependency>

+      <groupId>org.springframework.boot</groupId>

+      <artifactId>spring-boot-starter-json</artifactId>

+      <version>${springboot.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.springframework.boot</groupId>

+      <artifactId>spring-boot-starter-jdbc</artifactId>

+      <version>${springboot.version}</version>

+    </dependency>

+    <!-- End Spring Boot dependencies -->

+    <!-- Begin CADI AAF -->

+    <dependency>

+      <groupId>org.onap.aaf.authz</groupId>

+      <artifactId>aaf-auth-client</artifactId>

+      <version>${cadi.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.onap.aaf.authz</groupId>

+      <artifactId>aaf-cadi-core</artifactId>

+      <version>${cadi.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.onap.aaf.authz</groupId>

+      <artifactId>aaf-cadi-aaf</artifactId>

+      <version>${cadi.version}</version>

+    </dependency>

+    <!-- End CADI AAF -->

+    <dependency>

+      <groupId>com.h2database</groupId>

+      <artifactId>h2</artifactId>

+    </dependency>

+    <dependency>

+      <groupId>org.apache.commons</groupId>

+      <artifactId>commons-lang3</artifactId>

+      <version>3.4</version>

+    </dependency>

+    <dependency>

+      <groupId>org.codehaus.groovy</groupId>

+      <artifactId>groovy-all</artifactId>

+      <version>${groovy.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>com.google.code.gson</groupId>

+      <artifactId>gson</artifactId>

+      <version>${google.gson.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>com.google.guava</groupId>

+      <artifactId>guava</artifactId>

+      <version>${google.guava.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.python</groupId>

+      <artifactId>jython-standalone</artifactId>

+      <version>${python.version}</version>

+    </dependency>

+    <!-- MySQL Connector -->

+    <dependency>

+      <groupId>mysql</groupId>

+      <artifactId>mysql-connector-java</artifactId>

+      <version>8.0.14</version>

+    </dependency>

+    <dependency>

+      <artifactId>de.flapdoodle.embed.mongo</artifactId>

+      <groupId>de.flapdoodle.embed</groupId>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <artifactId>jersey-media-multipart</artifactId>

+      <groupId>org.glassfish.jersey.media</groupId>

+    </dependency>

+    <dependency>

+      <artifactId>junit</artifactId>

+      <groupId>junit</groupId>

+    </dependency>

+    <dependency>

+      <artifactId>httpclient</artifactId>

+      <groupId>org.apache.httpcomponents</groupId>

+      <version>4.5.7</version>

+    </dependency>

+    <dependency>

+      <artifactId>httpasyncclient</artifactId>

+      <groupId>org.apache.httpcomponents</groupId>

+      <version>4.1.4</version>

+    </dependency>

+    <dependency>

+      <artifactId>jackson-module-kotlin</artifactId>

+      <groupId>com.fasterxml.jackson.module</groupId>

+      <version>${jackson.version}</version>

+    </dependency>

+    <dependency>

+      <artifactId>jackson-datatype-jsr310</artifactId>

+      <groupId>com.fasterxml.jackson.datatype</groupId>

+      <version>${jackson.version}</version>

+    </dependency>

+    <dependency>

+      <groupId>org.apache.commons</groupId>

+      <artifactId>commons-vfs2</artifactId>

+      <version>2.2</version>

+    </dependency>

+    <dependency>

+      <groupId>com.jcraft</groupId>

+      <artifactId>jsch</artifactId>

+      <version>0.1.54</version>

+    </dependency>

+  </dependencies>

+  <parent>

+    <groupId>org.springframework.boot</groupId>

+    <artifactId>spring-boot-starter-parent</artifactId>

+    <version>2.1.4.RELEASE</version>

+  </parent>

+  <dependencyManagement>

+    <dependencies>

+      <dependency>

+        <!-- Import dependency management from camunda -->

+        <groupId>org.camunda.bpm</groupId>

+        <artifactId>camunda-bom</artifactId>

+        <version>${camunda.version}</version>

+        <scope>import</scope>

+        <type>pom</type>

+      </dependency>

+    </dependencies>

+  </dependencyManagement>

+  <modules>

+  </modules>

+  <properties>

+    <!-- Refer to the Camunda version compatibility matrix for choosing a version for a Spring Boot

+    Starter, Camunda BPM, and Spring Boot. -->

+    <skipTests>false</skipTests>

+    <skipITs>${skipTests}</skipITs>

+    <skipUTs>${skipTests}</skipUTs>

+

+    <cadi.version>2.1.10</cadi.version>

+    <docker.registry>registry.hub.docker.io</docker.registry>

+    <camunda.version>7.10.0-ee</camunda.version>

+    <camunda.bpm.assert.version>2.0-alpha2</camunda.bpm.assert.version>

+    <camunda.bpm.base.version>7.10.0</camunda.bpm.base.version>

+    <camunda.bpm.mail.version>1.1.0</camunda.bpm.mail.version>

+    <camunda.bpm.reactor.version>2.1.2</camunda.bpm.reactor.version>

+    <camunda.bpm.version>7.10.4-ee</camunda.bpm.version>

+    <camunda.bpm.external-task-client.version>1.1.1</camunda.bpm.external-task-client.version>

+    <camunda.mockito.version>3.2.1</camunda.mockito.version>

+    <camunda.spin.version>1.6.6</camunda.spin.version>

+    <camunda.springboot.version>3.2.0</camunda.springboot.version>

+    <google.guava.version>27.1-jre</google.guava.version>

+    <google.gson.version>2.8.5</google.gson.version>

+    <groovy.version>2.1.3</groovy.version>

+    <jackson.version>2.9.5</jackson.version>

+    <python.version>2.7.1</python.version>

+    <springboot.version>2.1.4.RELEASE</springboot.version>

+  </properties>

+  <build>

+    <finalName>otf-camunda</finalName>

+    <plugins>

+      <plugin>

+        <groupId>org.apache.maven.plugins</groupId>

+        <artifactId>maven-compiler-plugin</artifactId>

+        <configuration>

+          <source>1.8</source>

+          <target>1.8</target>

+        </configuration>

+      </plugin>

+      <plugin>

+        <groupId>org.springframework.boot</groupId>

+        <artifactId>spring-boot-maven-plugin</artifactId>

+        <version>${springboot.version}</version>

+        <configuration>

+          <requiresUnpack>

+            <dependency>

+              <groupId>org.python</groupId>

+              <artifactId>jython-standalone</artifactId>

+            </dependency>

+          </requiresUnpack>

+        </configuration>

+      </plugin>

+

+      <plugin>

+        <groupId>org.apache.maven.plugins</groupId>

+        <artifactId>maven-surefire-plugin</artifactId>

+        <version>2.22.1</version>

+        <configuration>

+          <skipTests>${skipUTs}</skipTests>

+        </configuration>

+      </plugin>

+      <plugin>

+        <groupId>org.apache.maven.plugins</groupId>

+        <artifactId>maven-failsafe-plugin</artifactId>

+        <version>2.22.1</version>

+        <executions>

+          <execution>

+            <id>run-integration-tests</id>

+            <phase>integration-test</phase>

+            <goals>

+              <goal>verify</goal>

+            </goals>

+          </execution>

+        </executions>

+        <configuration>

+          <skipTests>${skipTests}</skipTests>

+          <skipITs>${skipITs}</skipITs>

+        </configuration>

+      </plugin>

+

+    </plugins>

+    <resources>

+      <resource>

+        <directory>src/main/resources</directory>

+        <targetPath>${basedir}/target/src/main/resources</targetPath>

+        <filtering>true</filtering>

+        <includes>

+          <include>**/*</include>

+        </includes>

+        <excludes>

+          <exclude>otf_dev.p12</exclude>

+        </excludes>

+      </resource>

+      <resource>

+        <directory>src/main/resources</directory>

+        <filtering>true</filtering>

+        <includes>

+          <include>**/*</include>

+        </includes>

+        <excludes>

+          <exclude>otf_dev.p12</exclude>

+        </excludes>

+      </resource>

+      <resource>

+        <directory>src/main/resources</directory>

+        <targetPath>${basedir}/target/src/main/resources</targetPath>

+        <includes>

+          <include>otf_dev.p12</include>

+        </includes>

+      </resource>

+      <resource>

+        <directory>src/main/resources</directory>

+        <includes>

+          <include>otf_dev.p12</include>

+        </includes>

+      </resource>

+      <resource>

+        <directory>docker</directory>

+        <targetPath>${basedir}/target</targetPath>

+        <includes>

+          <include>Dockerfile</include>

+        </includes>

+      </resource>

+    </resources>

+  </build>

+  <name>otf-camunda</name>

+  <description>One of the core components of the Open Test Framework Test Control Unit.

+  </description>

+

+</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
index 0000000..7dfa547
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/Application.java
@@ -0,0 +1,100 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf;

+

+import java.util.List;

+import java.util.concurrent.Executor;

+import org.camunda.bpm.application.PostDeploy;

+import org.camunda.bpm.application.PreUndeploy;

+import org.camunda.bpm.application.ProcessApplicationInfo;

+import org.camunda.bpm.engine.ProcessEngine;

+import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.SpringApplication;

+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

+import org.springframework.boot.autoconfigure.SpringBootApplication;

+import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;

+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

+import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;

+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;

+import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.ComponentScan;

+import org.springframework.context.annotation.Primary;

+import org.springframework.scheduling.annotation.EnableAsync;

+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

+

+@SpringBootApplication

+@EnableAsync

+@ComponentScan(basePackages = "org.oran.otf")

+@EnableProcessApplication

+@EnableAutoConfiguration(

+    exclude = {

+        ErrorMvcAutoConfiguration.class,

+        DataSourceAutoConfiguration.class,

+        HibernateJpaAutoConfiguration.class,

+        MongoDataAutoConfiguration.class,

+        MongoAutoConfiguration.class

+    })

+public class Application {

+  public static void main(String[] args) {

+    SpringApplication.run(Application.class, args);

+

+  }

+

+  private static final Logger logger = LoggerFactory.getLogger(Application.class);

+

+  @Value("${otf.camunda.executor.async.core-pool-size}")

+  private int corePoolSize;

+

+  @Value("${otf.camunda.executor.async.max-pool-size}")

+  private int maxPoolSize;

+

+  @Value("${otf.camunda.executor.async.queue-capacity}")

+  private int queueCapacity;

+

+  private static final String LOGS_DIR = "logs_dir";

+

+

+  private static void setLogsDir() {

+    if (System.getProperty(LOGS_DIR) == null) {

+      System.getProperties().setProperty(LOGS_DIR, "./logs/camunda/");

+    }

+  }

+

+  @PostDeploy

+  public void postDeploy(ProcessEngine processEngineInstance) {}

+

+  @PreUndeploy

+  public void cleanup(ProcessEngine processEngine, ProcessApplicationInfo processApplicationInfo,

+      List<ProcessEngine> processEngines) {}

+

+  @Bean

+  @Primary

+  public Executor asyncExecutor() {

+    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

+    //executor.setTaskDecorator(new MDCTaskDecorator());

+    executor.setCorePoolSize(corePoolSize);

+    executor.setMaxPoolSize(maxPoolSize);

+    executor.setQueueCapacity(queueCapacity);

+    executor.setThreadNamePrefix("Camunda-");

+    executor.initialize();

+    return executor;

+  }

+}

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
index 0000000..d0b09ec
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/cadi/configuration/CadiFilterConfiguration.java
@@ -0,0 +1,97 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.cadi.configuration;

+

+import javax.servlet.Filter;

+import org.onap.aaf.cadi.Access.Level;

+import org.onap.aaf.cadi.config.Config;

+import org.onap.aaf.cadi.filter.CadiFilter;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.web.servlet.FilterRegistrationBean;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Conditional;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.context.annotation.PropertySource;

+

+@PropertySource("classpath:application.yaml")

+@Configuration

+@Conditional(value = FilterCondition.class)

+public class CadiFilterConfiguration {

+

+  @Value("${otf.cadi.aaf-mech-id}")

+  private String AAF_APPID;

+

+  @Value("${otf.cadi.aaf-mech-password}")

+  private String AAF_APPPASS;

+

+  @Value("${otf.cadi.hostname}")

+  private String CADI_HOSTNAME;

+

+  @Value("${otf.cadi.keyfile}")

+  private String CADI_KEYFILE;

+

+  @Value("${otf.ssl.keystore-path}")

+  private String CADI_KEYSTORE;

+

+  @Value("${otf.ssl.keystore-password}")

+  private String CADI_KEYSTORE_PASSWORD;

+

+  @Bean(name = "cadiFilterRegistrationBean")

+//  @ConditionalOnProperty(prefix = "otf.cadi", name = "enabled", havingValue = "true", matchIfMissing = true)

+  public FilterRegistrationBean<Filter> cadiFilterRegistration() {

+    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();

+    // set cadi configuration properties

+    initCadiProperties(registration);

+

+    registration.addUrlPatterns("/otf/tcu/*", "/rest/*");

+    registration.setFilter(cadiFilter());

+    registration.setName("otfCadiFilter");

+    registration.setOrder(0);

+    return registration;

+  }

+

+  Filter cadiFilter() {

+    return new CadiFilter();

+  }

+

+  private void initCadiProperties(FilterRegistrationBean<Filter> registration) {

+    registration.addInitParameter(Config.AAF_APPID, AAF_APPID);

+    registration.addInitParameter(Config.AAF_APPPASS, AAF_APPPASS);

+    registration.addInitParameter(Config.AAF_CALL_TIMEOUT, "10000");

+    registration.addInitParameter(Config.AAF_CONN_TIMEOUT, "6000");

+    registration.addInitParameter(Config.AAF_DEFAULT_REALM, "localhost");

+    registration.addInitParameter(Config.AAF_ENV, "PROD");

+    registration.addInitParameter(Config.AAF_LOCATE_URL, "https://localhost");

+    registration.addInitParameter(Config.AAF_LUR_CLASS, "org.onap.aaf.cadi.aaf.v2_0.AAFLurPerm");

+    registration.addInitParameter(

+        Config.AAF_URL, "https://localhost");

+

+    registration.addInitParameter(Config.BASIC_REALM, "localhost");

+    registration.addInitParameter(Config.BASIC_WARN, "true");

+

+    registration.addInitParameter(Config.CADI_KEYFILE, CADI_KEYFILE);

+    registration.addInitParameter(Config.CADI_LATITUDE, "38.62782");

+    registration.addInitParameter(Config.CADI_LOGLEVEL, Level.ERROR.name());

+    registration.addInitParameter(Config.CADI_LONGITUDE, "-90.19458");

+    registration.addInitParameter(Config.CADI_NOAUTHN, "/health/v1");

+    registration.addInitParameter(Config.CADI_PROTOCOLS, "TLSv1.1,TLSv1.2");

+    registration.addInitParameter(Config.CADI_KEYSTORE, CADI_KEYSTORE);

+    registration.addInitParameter(Config.CADI_KEYSTORE_PASSWORD, CADI_KEYSTORE_PASSWORD);

+

+    registration.addInitParameter(Config.HOSTNAME, CADI_HOSTNAME);

+  }

+}

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
index 0000000..d1f1515
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/cadi/configuration/FilterCondition.java
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.cadi.configuration;

+

+import org.springframework.context.annotation.Condition;

+import org.springframework.context.annotation.ConditionContext;

+import org.springframework.core.type.AnnotatedTypeMetadata;

+

+import java.lang.annotation.Annotation;

+

+public class FilterCondition implements Condition {

+    @Override

+    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {

+        String enabled = conditionContext.getEnvironment().getProperty("otf.cadi.enabled");

+        if (enabled == null)

+            return true;

+        return !enabled.equalsIgnoreCase("false");

+    }

+}

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
index 0000000..6649b12
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/cadi/configuration/OTFApiEnforcementFilterConfiguration.java
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.cadi.configuration;

+

+import org.oran.otf.cadi.filter.OTFApiEnforcementFilter;

+import javax.servlet.Filter;

+import javax.servlet.FilterConfig;

+import javax.servlet.ServletException;

+import org.onap.aaf.cadi.Access;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.web.servlet.FilterRegistrationBean;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Conditional;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.context.annotation.PropertySource;

+

+@PropertySource("classpath:application.yaml")

+@Configuration

+@Conditional(value = FilterCondition.class)

+public class OTFApiEnforcementFilterConfiguration {

+

+  @Value("${otf.cadi.aaf-perm-type}")

+  private String AAF_PERM_TYPE;

+

+  private Access access;

+  private FilterConfig fc;

+

+  @Bean(name = "otfApiEnforcementFilterRegistrationBean")

+//  @ConditionalOnProperty(prefix ="otf.cadi", name ="enabled", havingValue = "true" ,matchIfMissing = true)

+  @Conditional(value = FilterCondition.class)

+  public FilterRegistrationBean<Filter> otfApiEnforcementFilterRegistration()

+      throws ServletException {

+    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();

+    initFilterParameters(registration);

+

+    registration.addUrlPatterns("/otf/tcu/*", "/rest/*");

+    registration.setFilter(otfApiEnforcementFilter());

+    registration.setName("otfApiEnforcementFilter");

+    registration.setOrder(1);

+    return registration;

+  }

+

+  @Bean(name = "otfApiEnforcementFilter")

+  @Conditional(value = FilterCondition.class)

+//  @ConditionalOnProperty(prefix ="otf.cadi", name ="enabled", havingValue = "true", matchIfMissing = true)

+  Filter otfApiEnforcementFilter() throws ServletException {

+    return new OTFApiEnforcementFilter(access, AAF_PERM_TYPE);

+  }

+

+  private void initFilterParameters(FilterRegistrationBean<Filter> registration) {

+    registration.addInitParameter("aaf_perm_type", AAF_PERM_TYPE);

+  }

+}

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
index 0000000..cf04193
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/cadi/filter/OTFApiEnforcementFilter.java
@@ -0,0 +1,134 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.cadi.filter;

+

+import com.google.common.base.Strings;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.List;

+import java.util.Map;

+import java.util.TreeMap;

+import javax.servlet.Filter;

+import javax.servlet.FilterChain;

+import javax.servlet.FilterConfig;

+import javax.servlet.ServletException;

+import javax.servlet.ServletRequest;

+import javax.servlet.ServletResponse;

+import javax.servlet.http.HttpServletRequest;

+import javax.servlet.http.HttpServletResponse;

+import org.apache.commons.logging.Log;

+import org.apache.commons.logging.LogFactory;

+import org.onap.aaf.cadi.Access;

+import org.onap.aaf.cadi.Access.Level;

+import org.onap.aaf.cadi.ServletContextAccess;

+import org.onap.aaf.cadi.util.Split;

+

+public class OTFApiEnforcementFilter implements Filter {

+  private static final Log log = LogFactory.getLog(OTFApiEnforcementFilter.class);

+  private String type;

+  private Map<String, List<String>> publicPaths;

+  private Access access = null;

+

+  public OTFApiEnforcementFilter(Access access, String enforce) throws ServletException {

+    this.access = access;

+    init(enforce);

+  }

+

+  @Override

+  public void init(FilterConfig fc) throws ServletException {

+    init(fc.getInitParameter("aaf_perm_type"));

+    // need the Context for Logging, instantiating ClassLoader, etc

+    ServletContextAccess sca = new ServletContextAccess(fc);

+    if (access == null) {

+      access = sca;

+    }

+  }

+

+  private void init(final String ptypes) throws ServletException {

+    if (Strings.isNullOrEmpty(ptypes)) {

+      throw new ServletException("OTFApiEnforcement requires aaf_perm_type property");

+    }

+    String[] full = Split.splitTrim(';', ptypes);

+    if (full.length <= 0) {

+      throw new ServletException("aaf_perm_type property is empty");

+    }

+

+    type = full[0];

+    publicPaths = new TreeMap<>();

+    if (full.length > 1) {

+      for (int i = 1; i < full.length; ++i) {

+        String[] pubArray = Split.split(':', full[i]);

+        if (pubArray.length == 2) {

+          List<String> ls = publicPaths.get(pubArray[0]);

+          if (ls == null) {

+            ls = new ArrayList<>();

+            publicPaths.put(pubArray[0], ls);

+          }

+          ls.add(pubArray[1]);

+        }

+      }

+    }

+  }

+

+  @Override

+  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain fc)

+      throws IOException, ServletException {

+    HttpServletRequest hreq = (HttpServletRequest) req;

+    final String meth = hreq.getMethod();

+    String path = hreq.getContextPath(); // + hreq.getPathInfo();

+

+    if (Strings.isNullOrEmpty(path) || "null".equals(path)) {

+      path = hreq.getRequestURI().substring(hreq.getContextPath().length());

+    }

+

+    List<String> list = publicPaths.get(meth);

+    if (list != null) {

+      for (String p : publicPaths.get(meth)) {

+        if (path.startsWith(p)) {

+          access.printf(

+              Level.INFO,

+              "%s accessed public API %s %s\n",

+              hreq.getUserPrincipal().getName(),

+              meth,

+              path);

+          fc.doFilter(req, resp);

+          return;

+        }

+      }

+    }

+    if (hreq.isUserInRole(type + '|' + path + '|' + meth)) {

+      access.printf(

+          Level.INFO,

+          "%s is allowed access to %s %s\n",

+          hreq.getUserPrincipal().getName(),

+          meth,

+          path);

+      fc.doFilter(req, resp);

+    } else {

+      access.printf(

+          Level.AUDIT,

+          "%s is denied access to %s %s\n",

+          hreq.getUserPrincipal().getName(),

+          meth,

+          path);

+      ((HttpServletResponse) resp).sendError(HttpServletResponse.SC_UNAUTHORIZED);

+    }

+  }

+

+  @Override

+  public void destroy() {}

+}

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
index 0000000..f79550b
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFAuthorizationConfiguration.java
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.configuration;

+

+public class OTFAuthorizationConfiguration {

+

+}

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
index 0000000..87936f5
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFDataSourceConfiguration.java
@@ -0,0 +1,63 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.configuration;

+

+import javax.sql.DataSource;

+

+import com.zaxxer.hikari.HikariDataSource;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.jdbc.DataSourceBuilder;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.context.annotation.Primary;

+import org.springframework.jdbc.datasource.DataSourceTransactionManager;

+import org.springframework.transaction.PlatformTransactionManager;

+

+@Configuration

+public class OTFDataSourceConfiguration {

+  @Value("${otf.camunda.mysql.url}")

+  private String url;

+

+  @Value("${otf.camunda.mysql.username}")

+  private String username;

+

+  @Value("${otf.camunda.mysql.password}")

+  private String password;

+

+  @Bean

+  @Primary

+  public DataSource dataSource() {

+    DataSource dataSource = DataSourceBuilder.create()

+            .url(url)

+            .username(username)

+            .password(password)

+            .driverClassName("com.mysql.cj.jdbc.Driver")

+            .build();

+    if (dataSource instanceof HikariDataSource){

+//      ((HikariDataSource) dataSource).setLeakDetectionThreshold(10000);

+

+      ((HikariDataSource) dataSource).setMaximumPoolSize(75);

+      ((HikariDataSource) dataSource).setMinimumIdle(15);

+    }

+    return dataSource;

+  }

+

+  @Bean

+  public PlatformTransactionManager transactionManager() {

+    return new DataSourceTransactionManager(dataSource());

+  }

+}

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
index 0000000..40d2a74
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFDeploymentConfiguration.java
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.configuration;

+

+public class OTFDeploymentConfiguration {

+

+}

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
index 0000000..a8b62f9
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFFailedJobConfiguration.java
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.configuration;

+

+public class OTFFailedJobConfiguration {

+

+}

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
index 0000000..11b8705
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFJacksonDataConfigurator.java
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.configuration;

+

+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

+import com.fasterxml.jackson.module.kotlin.KotlinModule;

+import org.camunda.spin.impl.json.jackson.format.JacksonJsonDataFormat;

+import org.camunda.spin.spi.DataFormatConfigurator;

+import org.springframework.context.annotation.Configuration;

+import spinjar.com.fasterxml.jackson.databind.ObjectMapper;

+import spinjar.com.fasterxml.jackson.databind.module.SimpleModule;

+

+@Configuration

+public class OTFJacksonDataConfigurator implements DataFormatConfigurator<JacksonJsonDataFormat> {

+

+  @Override

+  public Class<JacksonJsonDataFormat> getDataFormatClass() {

+    return JacksonJsonDataFormat.class;

+  }

+

+  @Override

+  public void configure(JacksonJsonDataFormat dataFormat) {

+    ObjectMapper mapper = dataFormat.getObjectMapper();

+    SimpleModule module = new SimpleModule();

+    module.registerSubtypes(KotlinModule.class);

+    module.registerSubtypes(JavaTimeModule.class);

+    mapper.registerModule(module);

+  }

+}

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
index 0000000..6ece823
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFJobConfiguration.java
@@ -0,0 +1,73 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.configuration;

+

+import org.oran.otf.camunda.configuration.listener.OTFJobExecutorStartingEventListener;

+import org.camunda.bpm.engine.impl.jobexecutor.JobExecutor;

+import org.camunda.bpm.engine.spring.SpringProcessEngineConfiguration;

+import org.camunda.bpm.spring.boot.starter.configuration.impl.DefaultJobConfiguration;

+import org.camunda.bpm.spring.boot.starter.event.JobExecutorStartingEventListener;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;

+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.context.annotation.Primary;

+

+@Configuration

+public class OTFJobConfiguration extends DefaultJobConfiguration {

+  @Autowired protected JobExecutor jobExecutor;

+

+  @Override

+  protected void configureJobExecutor(SpringProcessEngineConfiguration configuration) {

+    int podNumber = -1;

+    String[] hostnameSplit = {"0", "0", "0"};

+

+    try {

+      String hostname = System.getenv("HOSTNAME");

+      hostnameSplit = hostname.split("-");

+      podNumber = Integer.parseInt(hostnameSplit[2]);

+    } catch (Exception e) {

+      podNumber = 1;

+    }

+

+    //if (podNumber == 1) {

+      camundaBpmProperties.getJobExecution().setLockTimeInMillis(43200000);

+      camundaBpmProperties.getJobExecution().setBackoffTimeInMillis(90);

+      camundaBpmProperties.getJobExecution().setMaxBackoff(450L);

+      camundaBpmProperties.getJobExecution().setWaitIncreaseFactor(2f);

+

+      super.configureJobExecutor(configuration);

+

+      configuration.getJobExecutor().setLockTimeInMillis(43200000);

+      configuration.getJobExecutor().setBackoffTimeInMillis(90);

+      configuration.getJobExecutor().setMaxBackoff(450L);

+      configuration.getJobExecutor().setWaitIncreaseFactor(2);

+

+

+      // configuration.getJobExecutor().setAutoActivate(false);

+   // }

+  }

+

+  @Bean

+  @Primary

+  @ConditionalOnProperty(prefix = "camunda.bpm.job-execution", name = "enabled", havingValue = "true", matchIfMissing = true)

+  @ConditionalOnBean(JobExecutor.class)

+  public static JobExecutorStartingEventListener jobExecutorStartingEventListener() {

+    return new OTFJobExecutorStartingEventListener();

+  }

+}

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
index 0000000..9715fc0
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OTFLoggingFeature.java
@@ -0,0 +1,238 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.configuration;

+

+import org.glassfish.jersey.logging.LoggingFeature;

+import org.glassfish.jersey.message.MessageUtils;

+

+import javax.ws.rs.WebApplicationException;

+import javax.ws.rs.client.ClientRequestContext;

+import javax.ws.rs.client.ClientRequestFilter;

+import javax.ws.rs.client.ClientResponseContext;

+import javax.ws.rs.client.ClientResponseFilter;

+import javax.ws.rs.container.ContainerRequestContext;

+import javax.ws.rs.container.ContainerRequestFilter;

+import javax.ws.rs.container.ContainerResponseContext;

+import javax.ws.rs.container.ContainerResponseFilter;

+import javax.ws.rs.core.FeatureContext;

+import javax.ws.rs.core.MultivaluedMap;

+import javax.ws.rs.ext.WriterInterceptor;

+import javax.ws.rs.ext.WriterInterceptorContext;

+import java.io.*;

+import java.net.URI;

+import java.nio.charset.Charset;

+import java.util.ArrayList;

+import java.util.Base64;

+import java.util.List;

+import java.util.Objects;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+public class OTFLoggingFeature extends LoggingFeature implements ContainerRequestFilter, ContainerResponseFilter,

+        ClientRequestFilter, ClientResponseFilter, WriterInterceptor {

+

+    private static final boolean printEntity = true;

+    private static final int maxEntitySize = 8 * 1024;

+    private final Logger logger = Logger.getLogger("OTFLoggingFeature");

+    private static final String ENTITY_LOGGER_PROPERTY = OTFLoggingFeature.class.getName();

+    private static final String NOTIFICATION_PREFIX = "* ";

+    private static final String REQUEST_PREFIX = "> ";

+    private static final String RESPONSE_PREFIX = "< ";

+    private static final String AUTHORIZATION = "Authorization";

+    private static final String EQUAL = " = ";

+    private static final String HEADERS_SEPARATOR = ", ";

+    private static List<String> requestHeaders;

+

+    static {

+        requestHeaders = new ArrayList<>();

+        requestHeaders.add(AUTHORIZATION);

+    }

+

+    public OTFLoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer maxEntitySize) {

+        super(logger, level, verbosity, maxEntitySize);

+    }

+

+    @Override

+    public boolean configure(FeatureContext context) {

+        context.register(this);

+        return true;

+    }

+

+    private Object getEmail(Object authorization){

+        try{

+            String encoded = ((String) authorization).split(" ")[1];

+            String decoded =  new String(Base64.getDecoder().decode(encoded));

+            return decoded.split(":")[0];

+        }

+        catch (Exception e){

+            return authorization;

+        }

+    }

+

+    @Override

+    public void filter(final ClientRequestContext context) {

+        final StringBuilder b = new StringBuilder();

+        printHeaders(b, context.getStringHeaders());

+        printRequestLine(b, "Sending client request", context.getMethod(), context.getUri());

+

+        if (printEntity && context.hasEntity()) {

+            final OutputStream stream = new LoggingStream(b, context.getEntityStream());

+            context.setEntityStream(stream);

+            context.setProperty(ENTITY_LOGGER_PROPERTY, stream);

+            // not calling log(b) here - it will be called by the interceptor

+        } else {

+            log(b);

+        }

+    }

+

+    @Override

+    public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {

+        final StringBuilder b = new StringBuilder();

+        printResponseLine(b, "Client response received", responseContext.getStatus());

+

+        if (printEntity && responseContext.hasEntity()) {

+            responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(),

+                    MessageUtils.getCharset(responseContext.getMediaType())));

+        }

+        log(b);

+    }

+

+    @Override

+    public void filter(final ContainerRequestContext context) throws IOException {

+        final StringBuilder b = new StringBuilder();

+        printHeaders(b, context.getHeaders());

+        printRequestLine(b, "Server has received a request", context.getMethod(), context.getUriInfo().getRequestUri());

+

+        if (printEntity && context.hasEntity()) {

+            context.setEntityStream(logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));

+        }

+        log(b);

+    }

+

+    @Override

+    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {

+        final StringBuilder b = new StringBuilder();

+        printResponseLine(b, "Server responded with a response", responseContext.getStatus());

+

+        if (printEntity && responseContext.hasEntity()) {

+            final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());

+            responseContext.setEntityStream(stream);

+            requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);

+            // not calling log(b) here - it will be called by the interceptor

+        } else {

+            log(b);

+        }

+    }

+

+    @Override

+    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {

+        final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);

+        writerInterceptorContext.proceed();

+        if (stream != null) {

+            log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));

+        }

+    }

+

+    private static class LoggingStream extends FilterOutputStream {

+        private final StringBuilder b;

+        private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

+

+        LoggingStream(final StringBuilder b, final OutputStream inner) {

+            super(inner);

+

+            this.b = b;

+        }

+

+        StringBuilder getStringBuilder(Charset charset) {

+            // write entity to the builder

+            final byte[] entity = byteArrayOutputStream.toByteArray();

+

+            b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));

+            if (entity.length > maxEntitySize) {

+                b.append("...more...");

+            }

+            b.append('\n');

+

+            return b;

+        }

+

+        public void write(final int i) throws IOException {

+            if (byteArrayOutputStream.size() <= maxEntitySize) {

+                byteArrayOutputStream.write(i);

+            }

+            out.write(i);

+        }

+    }

+

+    private void printHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {

+        for (String header : requestHeaders) {

+            if (Objects.nonNull(headers.get(header))) {

+                if(header.equalsIgnoreCase("Authorization")){

+                    b.append(header).append(EQUAL).append(getEmail(headers.get(header).get(0))).append(HEADERS_SEPARATOR);

+                }

+                else{

+                    b.append(header).append(EQUAL).append(headers.get(header)).append(HEADERS_SEPARATOR);

+                }

+            }

+        }

+        int lastIndex = b.lastIndexOf(HEADERS_SEPARATOR);

+        if (lastIndex != -1) {

+            b.delete(lastIndex, lastIndex + HEADERS_SEPARATOR.length());

+            b.append("\n");

+        }

+    }

+

+    private void log(final StringBuilder b) {

+        String message = b.toString();

+        if (logger != null) {

+            logger.info(message);

+        }

+    }

+

+    private void printRequestLine(final StringBuilder b, final String note, final String method, final URI uri) {

+        b.append(NOTIFICATION_PREFIX)

+                .append(note)

+                .append(" on thread ").append(Thread.currentThread().getId())

+                .append(REQUEST_PREFIX).append(method).append(" ")

+                .append(uri.toASCIIString()).append("\n");

+    }

+

+    private void printResponseLine(final StringBuilder b, final String note, final int status) {

+        b.append(NOTIFICATION_PREFIX)

+                .append(note)

+                .append(" on thread ").append(Thread.currentThread().getId())

+                .append(RESPONSE_PREFIX)

+                .append(Integer.toString(status))

+                .append("\n");

+    }

+

+    private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {

+        if (!stream.markSupported()) {

+            stream = new BufferedInputStream(stream);

+        }

+        stream.mark(maxEntitySize + 1);

+        final byte[] entity = new byte[maxEntitySize + 1];

+        final int entitySize = stream.read(entity);

+        b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));

+        if (entitySize > maxEntitySize) {

+            b.append("...more...");

+        }

+        b.append('\n');

+        stream.reset();

+        return stream;

+    }

+}
\ 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
index 0000000..a816786
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/OtfCamundaConfiguration.java
@@ -0,0 +1,155 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.configuration;

+

+import com.google.common.base.Strings;

+import java.util.Optional;

+import java.util.UUID;

+import javax.sql.DataSource;

+import org.camunda.bpm.application.impl.event.ProcessApplicationEventListenerPlugin;

+import org.camunda.bpm.engine.ProcessEngineConfiguration;

+import org.camunda.bpm.engine.impl.cfg.IdGenerator;

+import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;

+import org.camunda.bpm.engine.impl.history.HistoryLevel;

+import org.camunda.bpm.engine.spring.SpringProcessEngineConfiguration;

+import org.camunda.bpm.extension.reactor.bus.CamundaEventBus;

+import org.camunda.bpm.extension.reactor.plugin.ReactorProcessEnginePlugin;

+import org.camunda.bpm.extension.reactor.projectreactor.EventBus;

+import org.camunda.bpm.spring.boot.starter.configuration.impl.DefaultProcessEngineConfiguration;

+import org.camunda.connect.plugin.impl.ConnectProcessEnginePlugin;

+import org.camunda.spin.plugin.impl.SpinProcessEnginePlugin;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.beans.factory.annotation.Qualifier;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.transaction.PlatformTransactionManager;

+import org.springframework.util.StringUtils;

+

+@Configuration

+public class OtfCamundaConfiguration extends DefaultProcessEngineConfiguration {

+

+  @Autowired

+  private DataSource dataSource;

+  @Autowired

+  private PlatformTransactionManager transactionManager;

+  @Autowired private Optional<IdGenerator> idGenerator;

+

+  public static String processEngineName;

+

+  @Bean

+  public ProcessEngineConfiguration configureEngine(ProcessEngineConfigurationImpl configuration) {

+    configuration.setJavaSerializationFormatEnabled(true);

+    return configuration;

+  }

+

+  @Override

+  public void preInit(SpringProcessEngineConfiguration configuration) {

+

+    logger.info(configuration.getProcessEngineName());

+    processEngineName = System.getenv("HOSTNAME");

+    if (Strings.isNullOrEmpty(processEngineName)) {

+      processEngineName = "otf-camunda-" + UUID.randomUUID().toString();

+    }

+    processEngineName = processEngineName.replaceAll("-", "_");

+    camundaBpmProperties.setProcessEngineName(processEngineName);

+    camundaBpmProperties.setAutoDeploymentEnabled(true);

+    camundaBpmProperties.setHistoryLevel(HistoryLevel.HISTORY_LEVEL_FULL.getName());

+    camundaBpmProperties.setDefaultNumberOfRetries(1);

+

+    setProcessEngineName(configuration);

+    setDefaultSerializationFormat(configuration);

+    setIdGenerator(configuration);

+    setJobExecutorAcquireByPriority(configuration);

+    setDefaultNumberOfRetries(configuration);

+

+    configuration.setDataSource(dataSource);

+    configuration.setTransactionManager(transactionManager);

+    configuration.setHistory("true");

+    configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);

+    configuration.setJobExecutorActivate(true);

+    configuration.setCreateIncidentOnFailedJobEnabled(true);

+    configuration.setFailedJobListenerMaxRetries(0);

+    configuration.setJavaSerializationFormatEnabled(true);

+    configuration.setMetricsEnabled(false);

+  }

+

+  private void setIdGenerator(SpringProcessEngineConfiguration configuration) {

+    idGenerator.ifPresent(configuration::setIdGenerator);

+  }

+

+  private void setDefaultSerializationFormat(SpringProcessEngineConfiguration configuration) {

+    String defaultSerializationFormat = camundaBpmProperties.getDefaultSerializationFormat();

+    if (StringUtils.hasText(defaultSerializationFormat)) {

+      configuration.setDefaultSerializationFormat(defaultSerializationFormat);

+    } else {

+      logger.warn("Ignoring invalid defaultSerializationFormat='{}'", defaultSerializationFormat);

+    }

+  }

+

+  private void setProcessEngineName(SpringProcessEngineConfiguration configuration) {

+    String processEngineName =

+        StringUtils.trimAllWhitespace(camundaBpmProperties.getProcessEngineName());

+    if (!StringUtils.isEmpty(processEngineName) && !processEngineName.contains("-")) {

+      configuration.setProcessEngineName(processEngineName);

+    } else {

+      logger.warn(

+          "Ignoring invalid processEngineName='{}' - must not be null, blank or contain hyphen",

+          camundaBpmProperties.getProcessEngineName());

+    }

+  }

+

+  private void setJobExecutorAcquireByPriority(SpringProcessEngineConfiguration configuration) {

+    Optional.ofNullable(camundaBpmProperties.getJobExecutorAcquireByPriority())

+        .ifPresent(configuration::setJobExecutorAcquireByPriority);

+  }

+

+  private void setDefaultNumberOfRetries(SpringProcessEngineConfiguration configuration) {

+    Optional.ofNullable(camundaBpmProperties.getDefaultNumberOfRetries())

+        .ifPresent(configuration::setDefaultNumberOfRetries);

+  }

+

+  @Bean

+  CamundaEventBus camundaEventBus() {

+    return new CamundaEventBus();

+  }

+

+  @Bean

+  @Qualifier("camunda")

+  EventBus eventBus(final CamundaEventBus camundaEventBus) {

+    return camundaEventBus.get();

+  }

+

+  @Bean

+  ReactorProcessEnginePlugin reactorProcessEnginePlugin(final CamundaEventBus camundaEventBus) {

+    return new ReactorProcessEnginePlugin(camundaEventBus);

+  }

+

+  @Bean

+  ConnectProcessEnginePlugin connectProcessEnginePlugin() {

+    return new ConnectProcessEnginePlugin();

+  }

+

+  @Bean

+  SpinProcessEnginePlugin spinProcessEnginePlugin() {

+    return new SpinProcessEnginePlugin();

+  }

+

+  @Bean

+  ProcessApplicationEventListenerPlugin processApplicationEventListenerPlugin() {

+    return new ProcessApplicationEventListenerPlugin();

+  }

+}

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
index 0000000..d2d0194
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/configuration/listener/OTFJobExecutorStartingEventListener.java
@@ -0,0 +1,44 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.configuration.listener;

+

+import org.camunda.bpm.spring.boot.starter.event.JobExecutorStartingEventListener;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Value;

+

+public class OTFJobExecutorStartingEventListener extends JobExecutorStartingEventListener {

+

+    private static final Logger LOGGER = LoggerFactory.getLogger(OTFJobExecutorStartingEventListener.class);

+

+    @Value("${otf.camunda.executors-active}")

+    private boolean executorsActive;

+

+    protected void activate() {

+        if(!executorsActive){

+            LOGGER.info("job executor auto start disabled. otf.camunda.executors-active: " + this.executorsActive);

+            jobExecutor.shutdown();

+            return;

+        }

+        if (!jobExecutor.isActive()) {

+            jobExecutor.start();

+        } else {

+            LOGGER.info("job executor is already active");

+        }

+    }

+

+}

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
index 0000000..45511b0
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/CallTestHeadDelegate.java
@@ -0,0 +1,329 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.delegate.otf.common;

+

+import org.oran.otf.camunda.delegate.otf.common.runnable.TestHeadCallable;

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.workflow.utility.WorkflowTask;

+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;

+import org.oran.otf.common.model.*;

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.model.local.TestHeadNode;

+import org.oran.otf.common.model.local.TestHeadResult;

+import org.oran.otf.common.repository.*;

+import org.oran.otf.common.utility.Utility;

+import org.oran.otf.common.utility.database.Generic;

+import org.oran.otf.common.utility.permissions.PermissionChecker;

+import org.oran.otf.common.utility.permissions.UserPermission;

+import com.mongodb.client.result.UpdateResult;

+import java.util.ArrayList;

+import java.util.Collections;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.concurrent.ExecutorService;

+import java.util.concurrent.TimeUnit;

+

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.camunda.bpm.engine.delegate.JavaDelegate;

+import org.oran.otf.common.model.*;

+import org.oran.otf.common.repository.*;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+import org.springframework.stereotype.Component;

+

+@Component

+public class CallTestHeadDelegate implements JavaDelegate {

+  private static final Logger logger = LoggerFactory.getLogger(CallTestHeadDelegate.class);

+

+  @Autowired

+  private UserRepository userRepository;

+  @Autowired

+  private GroupRepository groupRepository;

+  @Autowired

+  private WorkflowUtility utility;

+  @Autowired

+  private TestDefinitionRepository testDefinitionRepository;

+  @Autowired

+  private TestHeadRepository testHeadRepository;

+  @Autowired

+  private TestInstanceRepository testInstanceRepository;

+  @Autowired

+  private MongoTemplate mongoOperation;

+

+  // Used to retrieve the results from test head runnables.

+  List<TestHeadResult> testHeadResults = Collections.synchronizedList(new ArrayList<>());

+

+  @Override

+  public void execute(DelegateExecution execution) throws Exception {

+    callTestHead(

+        execution.getCurrentActivityId(),

+        execution.getProcessDefinitionId(),

+        execution.getProcessInstanceId(),

+        execution.getProcessBusinessKey(),

+        execution.getVariables());

+  }

+

+  public void callTestHead(

+      String currentActivityId,

+      String processDefinitionId,

+      String processInstanceId,

+      String processBusinessKey,

+      Map<String, Object> variables)

+      throws Exception {

+    final String logPrefix = Utility.getLoggerPrefix();

+    logger.info(logPrefix + "::execute()");

+

+    // Get vthInput from the Camunda execution variable map.

+    List<Map<String, Object>> activityParameters = utility.getVthInput(variables, currentActivityId, logPrefix);

+

+    // Get the current test execution object.

+    TestExecution testExecution = utility.getTestExecution(variables, logPrefix);

+

+    // Lookup the test head before making computations in the loop, and before calling the runnable.

+    // If the lookup is made inside the runnable, concurrent test head calls would bombard the db.

+    TestHead testHead = getTestHead(testExecution, currentActivityId, processDefinitionId);

+

+    WorkflowTask workflowTask = new WorkflowTask(processInstanceId, activityParameters.size(), false);

+    ExecutorService pool = workflowTask.getPool();

+

+    // Try to cast each parameter to a Map, and create runnable tasks.

+    for (int i = 0; i < activityParameters.size(); i++) {

+      Object oTestHeadParameter = activityParameters.get(i);

+      Map<?, ?> mTestHeadParameter;

+      try {

+        mTestHeadParameter = Utility.toMap(oTestHeadParameter);

+        verifyOtfTestHead(mTestHeadParameter, testHead, testExecution, currentActivityId);

+      } catch (Exception e) {

+        // TODO: Make a design decision to either stop the execution, or attempt to convert the

+        // other parameters.

+        logger.error(

+            String.format(

+                "Unable to convert test head parameter at vthInput[%s][%d] to a Map.",

+                currentActivityId, i));

+        continue;

+      }

+

+      // Get all the arguments for the runnable.

+      Object oHeaders = mTestHeadParameter.get("headers"); // optional

+      Object oMethod = mTestHeadParameter.get("method"); // required

+      Object oPayload = mTestHeadParameter.get("payload"); // optional

+      Object oTimeoutInMillis = mTestHeadParameter.get("timeoutInMillis"); // optional

+

+      // Target typed parameters. Convert all objects to their expected types. Throw exceptions for

+      // required parameters, or for parameters that are provided but not of the expected type.

+      Map<String, String> headers = new HashMap<>();

+      String method = "";

+      Map<String, Object> payload = new HashMap<>();

+      int timeoutInMillis = 0;

+

+      if (oHeaders != null) {

+        try {

+          headers = (Map<String, String>) Utility.toMap(oHeaders);

+        } catch (Exception e) {

+          logger.error(

+              String.format(

+                  "Unable to convert test head parameter at vthInput[%s][%d][headers] to a Map.",

+                  currentActivityId, i));

+        }

+      }

+

+      if (oMethod == null) {

+        throw new TestExecutionException(

+            String.format(

+                "vthInput[%s][%d][method] is a required parameter.", currentActivityId, i));

+      } else {

+        try {

+          method = (String) oMethod;

+        } catch (ClassCastException cce) {

+          throw new TestExecutionException(

+              String.format(

+                  "Unable to read vthInput[%s][%d][method] as primitive type String.",

+                  processInstanceId, i));

+        }

+      }

+

+      if (oPayload != null) {

+        try {

+          payload = (Map<String, Object>) Utility.toMap(oPayload);

+        } catch (Exception e) {

+          logger.error(

+              String.format(

+                  "Unable to convert test head parameter at vthInput[%s][%d][payload] to a Map.",

+                  currentActivityId, i));

+        }

+      }

+

+      if (oTimeoutInMillis != null) {

+        try {

+          timeoutInMillis = (int) oTimeoutInMillis;

+        } catch (ClassCastException cce) {

+          throw new TestExecutionException(

+              String.format(

+                  "Unable to read vthInput[%s][%d][timeoutInMillis] as primitive type int.",

+                  currentActivityId, i));

+        }

+      }

+

+//      logger.info("{}(BEFORE) PRINTING THREAD INFORMATION", logPrefix);

+//      WorkflowTask.printThreadInformation();

+//      logger.info("{}(BEFORE) PRINTING WORKFLOW TASKS", logPrefix);

+//      WorkflowTask.printWorkflowTaskResources();

+      TestHeadCallable callable =

+          new TestHeadCallable(

+              timeoutInMillis,

+              method,

+              headers,

+              payload,

+              testHead,

+              currentActivityId,

+              testExecution,

+              mongoOperation);

+

+      // Submit the test head call to the executor service.

+      workflowTask.getFutures().add(pool.submit(callable));

+    }

+

+    // Prevent new tasks from being submitted, and allow running tasks to finish.

+    pool.shutdown();

+

+    int numResults = 0;

+    while (!pool.isTerminated()) {

+      try {

+        pool.awaitTermination(1, TimeUnit.SECONDS);

+      } catch (InterruptedException e) {

+        workflowTask.shutdown(true);

+        throw e;

+      }

+    }

+

+    workflowTask.shutdown(false);

+

+//    logger.info("{}(AFTER) PRINTING THREAD INFORMATION", logPrefix);

+//    WorkflowTask.printThreadInformation();

+//    logger.info("{}(AFTER) PRINTING WORKFLOW TASKS", logPrefix);

+//    WorkflowTask.printWorkflowTaskResources();

+  }

+

+  private void saveTestHeadResults(String businessKey) {

+    Query query = new Query();

+    query.addCriteria(Criteria.where("businessKey").is(businessKey));

+    Update update = new Update();

+    update.set("testHeadResults", testHeadResults);

+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+    // Check the status of the findAndUpdate database, and appropriately handle the errors.

+    if (result.getMatchedCount() == 0) {

+      throw new TestExecutionException(

+          String.format(

+              "Unable to log the test result because a testExecution associated with businessKey, %s, was not found.",

+              businessKey));

+    } else if (result.getModifiedCount() == 0) {

+      throw new TestExecutionException("Unable to persist the testExecution to the database.");

+    }

+  }

+

+  private TestHead getTestHead(

+      TestExecution testExecution, String currentActivityId, String processDefinitionId) {

+    List<BpmnInstance> bpmnInstances = testExecution.getHistoricTestDefinition().getBpmnInstances();

+    BpmnInstance bpmnInstance =

+        bpmnInstances.stream()

+            .filter(

+                _bpmnInstance ->

+                    _bpmnInstance.getProcessDefinitionId().equalsIgnoreCase(processDefinitionId))

+            .findFirst()

+            .orElse(null);

+

+    if (bpmnInstance == null) {

+      throw new TestExecutionException(

+          String.format(

+              "Error looking BpmnInstance with processDefinitionId %s.", processDefinitionId));

+    }

+

+    List<TestHeadNode> testHeads = bpmnInstance.getTestHeads();

+    TestHeadNode testHeadNode =

+        testHeads.stream()

+            .filter(testHead -> testHead.getBpmnVthTaskId().equals(currentActivityId))

+            .findAny()

+            .orElse(null);

+

+    if (testHeadNode == null) {

+      throw new TestExecutionException(

+          String.format(

+              "No test head associated with the currentActivityId %s.", currentActivityId));

+    }

+

+    TestHead testHead = Generic.findByIdGeneric(testHeadRepository, testHeadNode.getTestHeadId());

+    if (testHead == null) {

+      throw new TestExecutionException(

+          String.format(

+              "The test head with id, %s, was not found in the database.",

+              testHeadNode.getTestHeadId()));

+    }

+    User testExecUser = userRepository.findById(testExecution.getExecutorId().toString()).orElse(null);

+    Group testheadGroup =  groupRepository.findById(testHead.getGroupId().toString()).orElse(null);

+    if(testExecUser == null){

+      throw new TestExecutionException(

+              String.format("Can not find user, user id: %s",testExecution.getExecutorId().toString()));

+    }

+    if(testheadGroup == null){

+      throw new TestExecutionException(

+              String.format("Can not find test head group, group id: %s",testHead.getGroupId().toString())

+      );

+    }

+

+    if( (testHead.isPublic() != null && !testHead.isPublic()) &&

+            !PermissionChecker.hasPermissionTo(testExecUser,testheadGroup,UserPermission.Permission.EXECUTE,groupRepository)){

+      throw new TestExecutionException(

+              String.format(

+                      "User(%s) does not have permission to in testHead Group(%s)",

+                      testExecUser.get_id().toString(),testheadGroup.get_id().toString()

+              ));

+    }

+    return testHead;

+  }

+

+  private void verifyOtfTestHead(Map activityParams, TestHead testHead, TestExecution execution, String currentActivityId){

+    String testHeadName = testHead.getTestHeadName().toLowerCase();

+    switch(testHeadName) {

+      case "robot":

+        try {

+          TestInstance testInstance = Generic.findByIdGeneric(testInstanceRepository, execution.getHistoricTestInstance().get_id());

+          Map<String, Object> internalTestDataByActivity = (Map<String, Object>) testInstance.getInternalTestData().get(currentActivityId);

+          String robotFileId = (String) internalTestDataByActivity.get("robotFileId");

+          Map<String, Object> testData = new HashMap<>();

+          Map<String, Object> vthInput = new HashMap<>();

+          testData.put("robotFileId", robotFileId);

+          vthInput.put("testData", testData);

+          Map<String, Object> payload = (Map<String, Object>) activityParams.get("payload");

+          payload.put("vthInput", vthInput);

+        }

+        catch (Exception e){

+          throw new TestExecutionException(

+                  String.format(

+                          "Robot test head needs a robot file id: %s.", e.getMessage()));

+        }

+        break;

+      default:

+        break;

+    }

+  }

+}

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
index 0000000..0ecb37e
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/LogTestResultDelegate.java
@@ -0,0 +1,114 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.delegate.otf.common;

+

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.model.ExecutionConstants;

+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.repository.TestExecutionRepository;

+import org.oran.otf.common.utility.Utility;

+import com.mongodb.client.result.UpdateResult;

+

+import java.util.Arrays;

+import java.util.Date;

+import java.util.Map;

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.camunda.bpm.engine.delegate.JavaDelegate;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+import org.springframework.stereotype.Component;

+

+@Component

+public class LogTestResultDelegate implements JavaDelegate {

+

+  private static Logger logger = LoggerFactory.getLogger(LogTestResultDelegate.class);

+

+  @Autowired

+  private TestExecutionRepository testExecutionRepository;

+  @Autowired

+  private MongoTemplate mongoOperation;

+  @Autowired

+  private WorkflowUtility utility;

+

+  @Override

+  public void execute(DelegateExecution execution) throws Exception {

+    logger.info("[LogTestResult] Starting to log test result.");

+    final String logPrefix = Utility.getLoggerPrefix();

+    // Get the current test execution object.

+    TestExecution testExecution = utility.getTestExecution(execution.getVariables(), logPrefix);

+

+    // Set the end time right after retrieving the execution. This will not include the save time

+    // to the database.

+    testExecution.setEndTime(new Date(System.currentTimeMillis()));

+

+    // Set the processInstanceId because the user may have modified it through a script task.

+    testExecution.setProcessInstanceId(execution.getProcessInstanceId());

+

+    // Get the test result from the execution.

+    String testResult = utility.getTestResult(execution.getVariables(), logPrefix).toUpperCase();

+    if(testResult.equalsIgnoreCase(ExecutionConstants.TestResult.WORKFLOW_ERROR)){

+      testResult = ExecutionConstants.TestResult.ERROR;

+    }

+    if(Arrays.asList(ExecutionConstants.getAllTestResultStr()).contains(testResult.toUpperCase()))

+      testExecution.setTestResult(testResult.toUpperCase());

+    else{

+      testExecution.setTestResult(ExecutionConstants.TestResult.OTHER);

+    }

+

+    //Get the test result message from the execution

+    String testResultMessage = utility.getTestResultMessage(execution.getVariables(), logPrefix);

+    testExecution.setTestResultMessage(testResultMessage);

+

+    // Get test details as a String because it can be saved as one of many "JSON" types. Then try

+    // to convert it to a generic map.

+    Map<String, Object> testDetails = utility.getTestDetails(execution.getVariables(), logPrefix);

+    // Save the converted object to the test execution.

+    testExecution.setTestDetails(testDetails);

+

+

+    // Update the Test Execution object to save the result. Find the existing test execution by the

+    // processBusinessKey from the delegate execution because it is saved to the database before the

+    // user can modify the value.

+    Query query = new Query();

+    query.addCriteria(Criteria.where("businessKey").is(execution.getProcessBusinessKey()));

+    Update update = new Update();

+    update.set("testResult", testExecution.getTestResult());

+    update.set("testResultMessage", testExecution.getTestResultMessage());

+    update.set("testDetails", testExecution.getTestDetails());

+    update.set("endTime", testExecution.getEndTime());

+    update.set("processInstanceId", execution.getProcessInstanceId());

+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+    // Check the status of the findAndUpdate database, and appropriately handle the errors.

+    if (result.getMatchedCount() == 0) {

+      throw new TestExecutionException(

+          String.format(

+              "Unable to log the test result because a testExecution associated with businessKey, %s, was not found.",

+              execution.getProcessBusinessKey()));

+    } else if (result.getModifiedCount() == 0) {

+      throw new TestExecutionException("Unable to persist the testExecution to the database.");

+    } else {

+      logger.info(

+          logPrefix + execution.getProcessInstanceId() + ": Saved test result to the database.");

+    }

+  }

+}

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
index 0000000..41b9d8a
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/PostResultsToDMaaPDelegate.java
@@ -0,0 +1,159 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.delegate.otf.common;

+

+import org.oran.otf.cadi.configuration.FilterCondition;

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.model.ExecutionConstants;

+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.model.local.DMaaPRequest;

+import org.oran.otf.common.utility.Utility;

+import org.oran.otf.common.utility.gson.Convert;

+import org.oran.otf.common.utility.http.RequestUtility;

+import com.fasterxml.jackson.core.type.TypeReference;

+

+import java.util.Base64;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import javax.ws.rs.core.MediaType;

+import org.apache.http.HttpResponse;

+import org.apache.http.util.EntityUtils;

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.camunda.bpm.engine.delegate.JavaDelegate;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.context.annotation.Conditional;

+import org.springframework.stereotype.Component;

+

+@Component

+public class PostResultsToDMaaPDelegate implements JavaDelegate {

+

+  private static Logger logger = LoggerFactory.getLogger(PostResultsToDMaaPDelegate.class);

+

+  @Value("${otf.cadi.aaf-mech-id}")

+  private String AAF_APPID;

+

+  @Value("${otf.cadi.aaf-mech-password}")

+  private String AAF_APPPASS;

+

+  @Value("${otf.environment}")

+  private String env;

+

+  @Autowired private WorkflowUtility utility;

+

+  private final String template = "https://<hostname>:3905/events/<topic>";

+

+  @Override

+  public void execute(DelegateExecution execution) throws Exception {

+    logger.info("[PostResultsToDMaaP] Starting to post test results to dmaap.");

+    final String logPrefix = Utility.getLoggerPrefix();

+

+    // Get the current test execution object.

+    TestExecution testExecution = utility.getTestExecution(execution.getVariables(), logPrefix);

+

+    List<Object> testDataActivity = null;

+    Object dataByActivity =

+        utility.getTestDataByActivity(

+            execution.getVariables(), execution.getCurrentActivityId(), logPrefix);

+    if (!(dataByActivity instanceof List)) {

+      logger.error(

+          execution.getActivityInstanceId()

+              + ": Failed to retrieve dmaap requests in test data as list");

+      throw new TestExecutionException(

+          execution.getActivityInstanceId()

+              + ": Missing data to post to dmaap. Failed to retrieve dmaap requests in test data as list");

+    }

+

+    // convert data to map and grab dmaaprequest array

+    testDataActivity = (List) dataByActivity;

+    List<DMaaPRequest> dmaapRequests = null;

+    try {

+      dmaapRequests =

+          Convert.listToObjectList(testDataActivity, new TypeReference<List<DMaaPRequest>>() {});

+    } catch (Exception e) {

+      logger.error(

+          execution.getActivityInstanceId() + ": Failed to get dmaap requests from test data");

+      throw new TestExecutionException(

+          execution.getActivityInstanceId() + ": Missing data to post to dmaap. " + e.getMessage(),

+          e);

+    }

+    if (dmaapRequests == null || dmaapRequests.isEmpty()) {

+      logger.error(execution.getActivityInstanceId() + ": Failed to retrieve dmaap request list");

+      throw new TestExecutionException(

+          execution.getActivityInstanceId() + ": Missing dmaap request list");

+    }

+

+    // Get the testDetails object

+    Map<String, Object> testDetails = utility.getTestDetails(execution.getVariables(), logPrefix);

+

+    // Post results to Dmaap

+    Map<String, Object> results = postResultsToDmaap(testExecution, dmaapRequests, logPrefix);

+

+    // Set test details to show results of each post to dmaap

+    testDetails.put(execution.getCurrentActivityId(), results);

+    execution.setVariable(ExecutionConstants.ExecutionVariable.TEST_DETAILS, testDetails);

+    logger.info("[PostResultsToDMaaP] Finished posting test results to dmaap.");

+  }

+

+  private Map<String, Object> postResultsToDmaap(

+      TestExecution execution, List<DMaaPRequest> dmaapRequests, String logPrefix) {

+    String payload = execution.toString();

+    Map<String, Object> results = new HashMap<>();

+    Map<String, String> headers = new HashMap<>();

+    headers.put("Authorization", getAuthorizationHeader());

+    headers.put("Content-Type", MediaType.APPLICATION_JSON);

+

+    for (DMaaPRequest request : dmaapRequests) {

+      String url = new String(template);

+      url = url.replace("<hostname>", request.getHostname());

+      url = url.replace("<topic>", request.getAsyncTopic());

+

+      try {

+        results.put(url, getResponse(url, payload, headers, request.getRequiresProxy()));

+      } catch (Exception e) {

+        e.printStackTrace();

+        logger.info(logPrefix + "Error while posting to dmaap : " + e.getMessage());

+        results.put(url, e.getMessage());

+      }

+    }

+    return results;

+  }

+

+  private Map<String, Object> getResponse(

+      String url, String payload, Map<String, String> headers, boolean proxy)

+      throws Exception {

+    HttpResponse response = RequestUtility.postSync(url, payload, headers, proxy);

+    String sRes = EntityUtils.toString(response.getEntity());

+    Map<String, Object> res;

+    try {

+      res = Convert.jsonToMap(sRes);

+    } catch (Exception e) {

+      res = new HashMap<>();

+      res.put("response", sRes);

+    }

+    return res;

+  }

+

+  private String getAuthorizationHeader() {

+    return "Basic "

+        + new String(Base64.getEncoder().encode((AAF_APPID + ":" + AAF_APPPASS).getBytes()));

+  }

+}

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
index 0000000..7b90e7e
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/RunTestInstanceDelegate.java
@@ -0,0 +1,180 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.delegate.otf.common;

+

+import org.oran.otf.camunda.delegate.otf.common.runnable.SynchronousTestInstanceCallable;

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.workflow.WorkflowProcessor;

+import org.oran.otf.camunda.workflow.WorkflowRequest;

+import org.oran.otf.camunda.workflow.utility.WorkflowTask;

+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.model.local.ParallelFlowInput;

+import org.oran.otf.common.repository.TestExecutionRepository;

+import org.oran.otf.common.utility.Utility;

+import java.util.ArrayList;

+import java.util.Collections;

+import java.util.List;

+import java.util.Map;

+import java.util.concurrent.ExecutorService;

+import java.util.concurrent.TimeUnit;

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.camunda.bpm.engine.delegate.JavaDelegate;

+import org.oran.otf.camunda.model.ExecutionConstants;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.stereotype.Component;

+

+@Component

+public class RunTestInstanceDelegate implements JavaDelegate {

+

+  private final String logPrefix = Utility.getLoggerPrefix();

+  private final Logger logger = LoggerFactory.getLogger(RunTestInstanceDelegate.class);

+  // Used to retrieve the results from test head runnables.

+  private final List<TestExecution> testExecutions =

+      Collections.synchronizedList(new ArrayList<>());

+

+  private @Autowired

+  WorkflowUtility utility;

+  private @Autowired

+  TestExecutionRepository testExecutionRepository;

+  private @Autowired

+  WorkflowProcessor processor;

+  private @Autowired MongoTemplate mongoOperation;

+

+  @Override

+  public void execute(DelegateExecution execution) throws Exception {

+    runTestInstance(

+        execution.getCurrentActivityId(),

+        execution.getProcessInstanceId(),

+        execution.getVariables());

+  }

+

+  public void runTestInstance(

+      String currentActivityId, String processInstanceId, Map<String, Object> variables)

+      throws Exception {

+    @SuppressWarnings("unchecked")

+

+    // Get the current test execution object to pass as an argument to the callable, and for data

+    // stored in the historicTestInstance

+    TestExecution testExecution = utility.getTestExecution(variables, logPrefix);

+

+    // Get the parallel flow input

+    Map<String, ParallelFlowInput> pfloInput =

+        (Map<String, ParallelFlowInput>) variables.get("pfloInput");

+

+    if (!pfloInput.containsKey(currentActivityId)) {

+      throw new TestExecutionException(

+          String.format(

+              "%sCould not find activityId %s in pfloInput.", logPrefix, currentActivityId));

+    }

+

+    ParallelFlowInput parallelFlowInput = pfloInput.get(currentActivityId);

+    List<WorkflowRequest> args = parallelFlowInput.getArgs();

+    boolean interruptOnFailure = parallelFlowInput.isInterruptOnFailure();

+    int maxFailures = parallelFlowInput.getMaxFailures();

+    int threadPoolSize = parallelFlowInput.getThreadPoolSize();

+

+    WorkflowTask workflowTask =

+        new WorkflowTask(processInstanceId, threadPoolSize, interruptOnFailure);

+    ExecutorService pool = workflowTask.getPool();

+

+//    logger.info("{}(BEFORE) PRINTING THREAD INFORMATION", logPrefix);

+//    WorkflowTask.printThreadInformation();

+//    logger.info("{}(BEFORE) PRINTING WORKFLOW TASKS", logPrefix);

+//    WorkflowTask.printWorkflowTaskResources();

+

+    for (WorkflowRequest request : args) {

+      request.setExecutorId(testExecution.getExecutorId());

+      // If an inner workflow calls the parent workflow, there is a cyclic dependency. To prevent

+      // infinite test instances from being spawned, we need to check for cycles. This is only a top

+      // level check, but a more thorough check needs to be implemented after 1906.

+      if (request.getTestInstanceId() == testExecution.getHistoricTestInstance().get_id()) {

+        // Prevent new tasks from being submitted

+        pool.shutdown();

+        // Shutdown the thread pool, and cleanup threads.

+        workflowTask.shutdown(true);

+        break;

+      }

+

+      SynchronousTestInstanceCallable synchronousTestInstanceCallable =

+          new SynchronousTestInstanceCallable(

+              request, testExecution, testExecutionRepository, processor, mongoOperation);

+      workflowTask.getFutures().add(pool.submit(synchronousTestInstanceCallable));

+    }

+

+    // Prevent new tasks from being submitted, and allow running tasks to finish.

+    pool.shutdown();

+

+    // Wait for test instances to finish execution, and check for failures.

+    while (!pool.isTerminated()) {

+      try {

+        // Terminate tasks if the test execution failure limit is reached.

+        int numFailures;

+        synchronized (testExecution) {

+          numFailures = getNumberOfFailures(testExecution.getTestInstanceResults());

+        }

+

+        if (numFailures > maxFailures) {

+          logger.error(

+              String.format(

+                  "[PARENT-%s] Shutting down workflow - numFailures: %s, maxFailures: %s.",

+                  processInstanceId, numFailures, maxFailures));

+          workflowTask.shutdown();

+        }

+

+        pool.awaitTermination(1, TimeUnit.SECONDS);

+      } catch (InterruptedException e) {

+        throw e;

+      }

+    }

+

+    workflowTask.shutdown(false);

+

+//    logger.info("{}(AFTER) PRINTING THREAD INFORMATION", logPrefix);

+//    WorkflowTask.printThreadInformation();

+//    logger.info("{}(AFTER) PRINTING WORKFLOW TASKS", logPrefix);

+//    WorkflowTask.printWorkflowTaskResources();

+  }

+

+  // Gets the total number of testExecutions that have failed.

+  private int getNumberOfFailures(List<TestExecution> testExecutions) {

+    int numFailures = 0;

+

+    for (TestExecution testExecution : testExecutions) {

+      if (isTestFailed(testExecution)) {

+        numFailures++;

+      }

+    }

+

+    return numFailures;

+  }

+

+  // Checks if the testResult is marked as FAILED or FAILURE.

+  private boolean isTestFailed(TestExecution testExecution) {

+    String testResult = testExecution.getTestResult();

+    logger.debug(

+        String.format(

+            "[PARENT-%s] Test result for inner execution: %s.",

+            testExecution.getProcessInstanceId(), testExecution.getTestResult()));

+    return testResult.equalsIgnoreCase(ExecutionConstants.TestResult.FAILED)

+//        || testResult.equalsIgnoreCase(TestResult.FAILED)

+        || testResult.equalsIgnoreCase(ExecutionConstants.TestResult.TERMINATED);

+  }

+}

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
index 0000000..9755214
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/SendMailDelegate.java
@@ -0,0 +1,170 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.delegate.otf.common;

+

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.utility.Utility;

+import java.util.ArrayList;

+import java.util.List;

+import java.util.Map;

+import java.util.Properties;

+import javax.mail.Message;

+import javax.mail.MessagingException;

+import javax.mail.Session;

+import javax.mail.Transport;

+import javax.mail.internet.InternetAddress;

+import javax.mail.internet.MimeMessage;

+import org.assertj.core.util.Strings;

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.camunda.bpm.engine.delegate.JavaDelegate;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Service;

+

+@Service

+public class SendMailDelegate implements JavaDelegate {

+

+  @Autowired private WorkflowUtility utility;

+

+  private static final String logPrefix = Utility.getLoggerPrefix();

+  private static final Logger log = LoggerFactory.getLogger(SendMailDelegate.class);

+

+  @Override

+  public void execute(DelegateExecution execution) throws Exception {

+    Map<String, Object> variables = execution.getVariables();

+    Map<String, Object> testData = utility.getTestData(variables, logPrefix);

+

+    Map<String, Object> sendMailData = null;

+    try {

+      sendMailData =

+          (Map<String, Object>) testData.getOrDefault(execution.getCurrentActivityId(), null);

+    } catch (Exception e) {

+      log.error(e.getMessage());

+      throw new TestExecutionException(e);

+    }

+

+    if (sendMailData == null) {

+      String err =

+          String.format(

+              "%sMissing parameters for activityId, %s.",

+              logPrefix, execution.getCurrentActivityId());

+      log.error(err);

+      throw new TestExecutionException(err);

+    }

+

+    // Get the recipient(s)

+    Object oRecipients = sendMailData.get("to");

+    if (oRecipients == null) {

+      String err = String.format("%sRecipients array cannot be null or empty.", logPrefix);

+      log.error(err);

+      throw new TestExecutionException(err);

+    }

+    List<String> recipients = null;

+    try {

+      recipients = (ArrayList<String>) (oRecipients);

+      if (recipients.size() == 0) {

+        String err = String.format("%sRecipients array cannot be null or empty.", logPrefix);

+        log.error(err);

+        throw new TestExecutionException(err);

+      }

+    } catch (Exception e) {

+      throw new TestExecutionException(e);

+    }

+

+    for (String recipient : recipients) {

+      if (Strings.isNullOrEmpty(recipient)) {

+        String err = String.format("%sRecipient cannot be null or empty.", logPrefix);

+        log.error(err);

+        throw new TestExecutionException(err);

+      }

+    }

+

+    // Get the email subject.

+    String subject = (String) sendMailData.get("subject");

+    if (Strings.isNullOrEmpty(subject.trim())) {

+      String err = String.format("%sSubject cannot be null or empty.", logPrefix);

+      log.error(err);

+      throw new TestExecutionException(err);

+    }

+

+    // Get the body contents.

+    String body = (String) sendMailData.get("body");

+    if (Strings.isNullOrEmpty(body.trim())) {

+      String err = String.format("%sBody cannot be null or empty.", logPrefix);

+      log.error(err);

+      throw new TestExecutionException(err);

+    }

+

+    TestExecution testExecution = utility.getTestExecution(variables, logPrefix);

+    String sender = testExecution.getHistoricEmail();

+    String hTestInstanceId = testExecution.getHistoricTestInstance().get_id().toString();

+    String processInstanceId = execution.getProcessInstanceId();

+

+    sendMail(recipients, subject, body, sender, processInstanceId, hTestInstanceId);

+  }

+

+  public void sendMail(

+      List<String> recipients,

+      String subject,

+      String body,

+      String sender,

+      String processInstanceId,

+      String testInstanceId)

+      throws Exception {

+    // Get the system properties.

+    Properties properties = System.getProperties();

+

+    // Set the SMTP host.

+    properties.setProperty("mail.smtp.host", "localhost");

+

+    // creating session object to get properties

+    Session session = Session.getDefaultInstance(properties);

+

+    try {

+      // MimeMessage object.

+      MimeMessage message = new MimeMessage(session);

+

+      // Set From Field: adding senders email to from field.

+      message.setFrom(new InternetAddress("OTF_EMAIL-ALERT@localhost"));

+

+      // Set Subject: subject of the email

+      message.setSubject(subject);

+

+      // set body of the email.

+      StringBuffer sb = new StringBuffer();

+      sb.append("************************OTF Alerting System************************");

+      sb.append("\n\n");

+      sb.append(String.format("This message was sent by %s via the Open Test Framework\n", sender));

+      sb.append(String.format("processInstanceId: %s\n", processInstanceId));

+      sb.append(String.format("testInstanceId: %s", testInstanceId));

+      sb.append("\n\n");

+      sb.append("******************************************************************");

+      sb.append("\n\n");

+      sb.append(body);

+

+      message.setText(sb.toString());

+

+      // Send email.

+      Transport.send(message);

+    } catch (MessagingException mex) {

+      mex.printStackTrace();

+    }

+  }

+}

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
index 0000000..680688c
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/AsynchronousTestInstanceCallable.java
@@ -0,0 +1,199 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.delegate.otf.common.runnable;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.exception.WorkflowProcessorException;

+import org.oran.otf.camunda.service.ProcessEngineAwareService;

+import org.oran.otf.camunda.workflow.WorkflowProcessor;

+import org.oran.otf.camunda.workflow.WorkflowRequest;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.repository.TestExecutionRepository;

+import org.oran.otf.common.utility.database.TestExecutionUtility;

+import com.mongodb.client.result.UpdateResult;

+import java.util.Timer;

+import java.util.TimerTask;

+import java.util.concurrent.Callable;

+

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.runtime.ProcessInstance;

+import org.oran.otf.camunda.model.ExecutionConstants;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+

+public class AsynchronousTestInstanceCallable extends ProcessEngineAwareService

+    implements Callable<TestExecution> {

+

+  private static final Logger logger =

+      LoggerFactory.getLogger(AsynchronousTestInstanceCallable.class);

+  private final TestExecution parentTestExecution;

+  private final TestExecutionRepository testExecutionRepository;

+  private final WorkflowProcessor processor;

+  private final MongoTemplate mongoOperation;

+

+  private final WorkflowRequest request;

+  private String processInstanceId;

+

+  public AsynchronousTestInstanceCallable(

+      WorkflowRequest request,

+      TestExecution parentTestExecution,

+      TestExecutionRepository testExecutionRepository,

+      WorkflowProcessor processor,

+      MongoTemplate mongoOperation) {

+    this.request = request;

+    this.parentTestExecution = parentTestExecution;

+

+    this.processInstanceId = "";

+

+    this.testExecutionRepository = testExecutionRepository;

+    this.processor = processor;

+    this.mongoOperation = mongoOperation;

+  }

+

+  public AsynchronousTestInstanceCallable(

+      WorkflowRequest request,

+      TestExecutionRepository testExecutionRepository,

+      WorkflowProcessor processor,

+      MongoTemplate mongoOperation) {

+    this.request = request;

+    this.parentTestExecution = null;

+

+    this.processInstanceId = "";

+

+    this.testExecutionRepository = testExecutionRepository;

+    this.processor = processor;

+    this.mongoOperation = mongoOperation;

+  }

+

+  @Override

+  public TestExecution call() throws WorkflowProcessorException {

+    try {

+      TestExecution initialTestExecution = processor.processWorkflowRequest(request);

+      this.processInstanceId = initialTestExecution.getProcessInstanceId();

+

+      // Create a timer task that will call the cancellation after the specified time.

+      TimerTask abortTestInstanceTask =

+          new TimerTask() {

+            @Override

+            public void run() {

+              cancelProcessInstance(processInstanceId);

+

+              // Find the result after the process instance after it has finished.

+              TestExecution testExecution =

+                  testExecutionRepository

+                      .findFirstByProcessInstanceId(processInstanceId)

+                      .orElse(null);

+              if (testExecution == null) {

+                logger.error(

+                    String.format(

+                        "Process instance with id %s completed, however, a corresponding test execution was not found in the database.",

+                        processInstanceId));

+              } else {

+                testExecution.setTestResult(ExecutionConstants.TestResult.TERMINATED);

+                TestExecutionUtility.saveTestResult(

+                    mongoOperation, testExecution, testExecution.getTestResult());

+

+                // Saves the testExecution to the parent test execution if this belongs to a "sub"

+                // test

+                // instance call.

+                // updated terminated

+                saveToParentTestExecution(testExecution);

+              }

+            }

+          };

+

+      // Start the daemon that waits the max time for a running test instance.

+      long maxExecutionTimeInMillis = request.getMaxExecutionTimeInMillis();

+      if (maxExecutionTimeInMillis > 0) {

+        new Timer(true).schedule(abortTestInstanceTask, maxExecutionTimeInMillis);

+      }

+

+      return initialTestExecution;

+    } catch (WorkflowProcessorException e) {

+      throw e;

+    } catch (Exception e) {

+      e.printStackTrace();

+      return null;

+    }

+  }

+

+  private void saveToParentTestExecution(TestExecution testExecution) {

+    if (parentTestExecution == null) {

+      return;

+    }

+

+    synchronized (parentTestExecution) {

+      // Add the testExecution to the parentTestExecution

+      parentTestExecution.getTestInstanceResults().add(testExecution);

+      Query query = new Query();

+      query.addCriteria(Criteria.where("_id").is(parentTestExecution.get_id()));

+      // Also add businessKey as a criteria because the object won't be found if the business key

+      // was somehow modified in the workflow.

+      query.addCriteria(Criteria.where("businessKey").is(parentTestExecution.getBusinessKey()));

+      Update update = new Update();

+      update.set("testInstanceResults", parentTestExecution.getTestInstanceResults());

+      UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+      // Check the status of the findAndUpdate database, and appropriately handle the errors.

+      if (result.getMatchedCount() == 0) {

+        throw new TestExecutionException(

+            String.format(

+                "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",

+                parentTestExecution.get_id(), parentTestExecution.getBusinessKey()));

+      } else if (result.getModifiedCount() == 0) {

+        throw new TestExecutionException("Unable to persist the testExecution to the database.");

+      }

+    }

+  }

+

+  private boolean isProcessInstanceEnded(String processInstanceId) {

+    try {

+      RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();

+      ProcessInstance processInstance =

+          runtimeService

+              .createProcessInstanceQuery()

+              .processInstanceId(processInstanceId)

+              .singleResult();

+      return processInstance == null || processInstance.isEnded();

+    } catch (Exception e) {

+      logger.error("Exception :", e);

+      return true;

+    }

+  }

+

+  private boolean cancelProcessInstance(String processInstanceId) {

+    try {

+      RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();

+      runtimeService.deleteProcessInstance(

+          processInstanceId, "Triggered by user defined max execution time timeout.");

+      ProcessInstance processInstance =

+          runtimeService

+              .createProcessInstanceQuery()

+              .processInstanceId(processInstanceId)

+              .singleResult();

+      return processInstance == null || processInstance.isEnded();

+    } catch (Exception e) {

+      logger.error("Exception :", e);

+      return true;

+    }

+  }

+}

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
index 0000000..cffdc1e
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/SynchronousTestInstanceCallable.java
@@ -0,0 +1,227 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.delegate.otf.common.runnable;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.exception.WorkflowProcessorException;

+import org.oran.otf.camunda.service.ProcessEngineAwareService;

+import org.oran.otf.camunda.workflow.WorkflowProcessor;

+import org.oran.otf.camunda.workflow.WorkflowRequest;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.repository.TestExecutionRepository;

+import org.oran.otf.common.utility.database.TestExecutionUtility;

+import com.mongodb.client.result.UpdateResult;

+import java.util.Collections;

+import java.util.HashMap;

+import java.util.Map;

+import java.util.Timer;

+import java.util.TimerTask;

+import java.util.concurrent.Callable;

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.runtime.ProcessInstance;

+import org.oran.otf.camunda.model.ExecutionConstants;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+

+public class SynchronousTestInstanceCallable extends ProcessEngineAwareService

+    implements Callable<TestExecution> {

+

+  private static final Logger logger =

+      LoggerFactory.getLogger(SynchronousTestInstanceCallable.class);

+  private final TestExecution parentTestExecution;

+  private final TestExecutionRepository testExecutionRepository;

+  private final WorkflowProcessor processor;

+  private final MongoTemplate mongoOperation;

+

+  private final WorkflowRequest request;

+  private String processInstanceId;

+

+  public SynchronousTestInstanceCallable(

+      WorkflowRequest request,

+      TestExecution parentTestExecution,

+      TestExecutionRepository testExecutionRepository,

+      WorkflowProcessor processor,

+      MongoTemplate mongoOperation) {

+    this.request = request;

+    this.parentTestExecution = parentTestExecution;

+

+    this.processInstanceId = "";

+

+    this.testExecutionRepository = testExecutionRepository;

+    this.processor = processor;

+    this.mongoOperation = mongoOperation;

+  }

+

+  public SynchronousTestInstanceCallable(

+      WorkflowRequest request,

+      TestExecutionRepository testExecutionRepository,

+      WorkflowProcessor processor,

+      MongoTemplate mongoOperation) {

+    this.request = request;

+    this.parentTestExecution = null;

+

+    this.processInstanceId = "";

+

+    this.testExecutionRepository = testExecutionRepository;

+    this.processor = processor;

+    this.mongoOperation = mongoOperation;

+  }

+

+  @Override

+  public TestExecution call() throws WorkflowProcessorException {

+    try {

+      TestExecution initialTestExecution = processor.processWorkflowRequest(request);

+      this.processInstanceId = initialTestExecution.getProcessInstanceId();

+      final Map<String, Boolean> abortionStatus = Collections.synchronizedMap(new HashMap<>());

+      abortionStatus.put("isAborted", false);

+

+      // Create a timer task that will call the cancellation after the specified time.

+      TimerTask abortTestInstanceTask =

+          new TimerTask() {

+            @Override

+            public void run() {

+              cancelProcessInstance(processInstanceId);

+              abortionStatus.put("isAborted", true);

+            }

+          };

+

+      // Start the daemon that waits the max time for a running test instance.

+      long maxExecutionTimeInMillis = request.getMaxExecutionTimeInMillis();

+      if (maxExecutionTimeInMillis > 0) {

+        new Timer(true).schedule(abortTestInstanceTask, maxExecutionTimeInMillis);

+      }

+

+      while (!isProcessInstanceEnded(processInstanceId)) {

+        Thread.sleep(1000);

+      }

+

+      // Find the result after the process instance after it has finished.

+      TestExecution testExecution =

+          testExecutionRepository.findFirstByProcessInstanceId(processInstanceId).orElse(null);

+      if (testExecution == null) {

+        logger.error(

+            String.format(

+                "Process instance with id %s completed, however, a corresponding test execution was not found in the database.",

+                processInstanceId));

+      } else {

+        // If the test result was not set in the workflow, set it to completed now that we know the

+        // process instance has finished executing.

+        String testResult = testExecution.getTestResult();

+        if (testResult.equalsIgnoreCase("UNKNOWN") || testResult.equalsIgnoreCase("STARTED")) {

+          if (abortionStatus.get("isAborted")) {

+            testExecution.setTestResult(ExecutionConstants.TestResult.TERMINATED);

+          } else {

+            testExecution.setTestResult(ExecutionConstants.TestResult.COMPLETED);

+          }

+

+          //TODO: RG remove prints

+          System.out.println(testExecution.getTestHeadResults());

+          System.out.println(request);

+          TestExecutionUtility.saveTestResult(

+              mongoOperation, testExecution, testExecution.getTestResult());

+        }

+

+        // Saves the testExecution to the parent test execution if this belongs to a "sub" test

+        // instance call.

+        saveToParentTestExecution(testExecution);

+      }

+

+      return testExecution;

+    } catch (WorkflowProcessorException e) {

+      throw e;

+    } catch (Exception e) {

+      e.printStackTrace();

+      return null;

+    }

+  }

+

+  private void saveToParentTestExecution(TestExecution testExecution) {

+    if (parentTestExecution == null) {

+      return;

+    }

+

+    synchronized (parentTestExecution) {

+      // Add the testExecution to the parentTestExecution

+      parentTestExecution.getTestInstanceResults().add(testExecution);

+      Query query = new Query();

+      query.addCriteria(Criteria.where("_id").is(parentTestExecution.get_id()));

+      // Also add businessKey as a criteria because the object won't be found if the business key

+      // was somehow modified in the workflow.

+      query.addCriteria(Criteria.where("businessKey").is(parentTestExecution.getBusinessKey()));

+      Update update = new Update();

+      update.set("testInstanceResults", parentTestExecution.getTestInstanceResults());

+      UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+      // Check the status of the findAndUpdate database, and appropriately handle the errors.

+      if (result.getMatchedCount() == 0) {

+        throw new TestExecutionException(

+            String.format(

+                "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",

+                parentTestExecution.get_id(), parentTestExecution.getBusinessKey()));

+      } else if (result.getModifiedCount() == 0) {

+        throw new TestExecutionException("Unable to persist the testExecution to the database.");

+      }

+    }

+    logger.info(

+        String.format(

+            "\t[Child-%s] finished saving result to parentTestExecution with result %s.",

+            processInstanceId, testExecution.getTestResult()));

+  }

+

+  private boolean isProcessInstanceEnded(String processInstanceId) {

+    try {

+      RuntimeService runtimeService =

+          BpmPlatform.getProcessEngineService()

+              .getProcessEngine(OtfCamundaConfiguration.processEngineName)

+              .getRuntimeService();

+      ProcessInstance processInstance =

+          runtimeService

+              .createProcessInstanceQuery()

+              .processInstanceId(processInstanceId)

+              .singleResult();

+      return processInstance == null || processInstance.isEnded();

+    } catch (Exception e) {

+      logger.error("Exception :", e);

+      return true;

+    }

+  }

+

+  private boolean cancelProcessInstance(String processInstanceId) {

+    try {

+      RuntimeService runtimeService =

+          BpmPlatform.getProcessEngineService()

+              .getProcessEngine(OtfCamundaConfiguration.processEngineName)

+              .getRuntimeService();

+      runtimeService.deleteProcessInstance(

+          processInstanceId, "Triggered by user defined max execution time timeout.");

+      ProcessInstance processInstance =

+          runtimeService

+              .createProcessInstanceQuery()

+              .processInstanceId(processInstanceId)

+              .singleResult();

+      return processInstance == null || processInstance.isEnded();

+    } catch (Exception e) {

+      logger.error("Exception :", e);

+      return true;

+    }

+  }

+}

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
index 0000000..d0ee267
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/delegate/otf/common/runnable/TestHeadCallable.java
@@ -0,0 +1,267 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.delegate.otf.common.runnable;

+

+

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.model.TestHead;

+import org.oran.otf.common.model.local.TestHeadRequest;

+import org.oran.otf.common.model.local.TestHeadResult;

+import org.oran.otf.common.utility.Utility;

+import org.oran.otf.common.utility.gson.Convert;

+import org.oran.otf.common.utility.http.RequestUtility;

+import com.google.common.base.Strings;

+import com.google.gson.JsonParser;

+import com.mongodb.client.result.UpdateResult;

+import java.io.IOException;

+import java.util.Date;

+import java.util.HashMap;

+import java.util.Map;

+import java.util.concurrent.Callable;

+

+import org.apache.http.HttpHeaders;

+import org.apache.http.HttpResponse;

+import org.apache.http.util.EntityUtils;

+import org.oran.otf.common.utility.http.HeadersUtility;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+

+// TODO : Create a constructor that does not take a testexecution object as a parameter. This means

+// that the result should only be returned, and the call to saveResult should be avoided.

+public class TestHeadCallable implements Callable<TestHeadResult> {

+

+  private static Logger logger = LoggerFactory.getLogger(TestHeadCallable.class);

+  private final String logPrefix = Utility.getLoggerPrefix();

+  private final TestExecution testExecution;

+

+  private final int timeoutInMillis;

+  private final String httpMethod;

+  private final Map<String, String> headers;

+  private final Map<String, Object> body;

+  private final TestHead testHead;

+  private final String activityId;

+

+  private final MongoTemplate mongoOperation;

+

+  private String url;

+  private TestHeadResult result;

+  private Date startTime;

+  private Date endTime;

+

+  public TestHeadCallable(

+      int timeoutInMillis,

+      String httpMethod,

+      Map<String, String> headers,

+      Map<String, Object> vthInput,

+      TestHead testHead,

+      String activityId,

+      TestExecution testExecution,

+      MongoTemplate mongoOperation) {

+    this.timeoutInMillis = timeoutInMillis;

+    this.httpMethod = httpMethod;

+    this.headers = headers;

+    this.body = vthInput;

+    this.testHead = testHead;

+    this.activityId = activityId;

+    this.testExecution = testExecution;

+

+    this.mongoOperation = mongoOperation;

+

+    // Generate the url after the test head is set.

+    this.url = generateUrl();

+  }

+

+  @Override

+  public TestHeadResult call() throws Exception {

+    // If simulation mode is set, then send back expected result after expected delay

+    if (testExecution.getHistoricTestInstance().isSimulationMode()) {

+      logger.info(logPrefix + "Start call to test head in simulation mode.");

+      startTime = new Date(System.currentTimeMillis());

+      Map<String, Object> response =

+          simulateVTH(

+              this.activityId, testExecution.getHistoricTestInstance().getSimulationVthInput());

+      endTime = new Date(System.currentTimeMillis());

+      logger.info(logPrefix + "Finished call to test head in simulation mode.");

+

+      //TODO: This will need to change if enhancement is made to allow status codes

+      TestHeadResult result = generateResult(response);

+      testExecution.getTestHeadResults().add(result);

+      saveResult(testExecution);

+      return result;

+    }

+    logger.info(logPrefix + "Start call to test head.");

+    HttpResponse response = null;

+    TestHeadResult result = null;

+    // Set the start time right before the request.

+    startTime = new Date(System.currentTimeMillis());

+

+    // add api key to headers if required

+    setApiKeyIfEnabled();

+

+    //TODO RG Added to slow Execution

+    //Thread.sleep(60000);

+

+    try {

+      switch (httpMethod.toLowerCase()) {

+        case "post":

+          response =

+              timeoutInMillis > 0

+                  ? RequestUtility.postSync(

+                      url, Convert.mapToJson(body), headers, timeoutInMillis, false)

+                  : RequestUtility.postSync(url, Convert.mapToJson(body), headers, false);

+          break;

+        case "get":

+          response =

+              timeoutInMillis > 0

+                  ? RequestUtility.getSync(url, headers, timeoutInMillis, false)

+                  : RequestUtility.getSync(url, headers, false);

+          break;

+        default:

+          throw new RuntimeException();

+      }

+      // Set the end time when the request returns.

+      endTime = new Date(System.currentTimeMillis());

+      logger.info(logPrefix + "Finished call to test head.");

+

+      // Generate and return the result.

+      result = generateResult(response);

+    } catch (Exception e) {

+      Map<String, Object> error = new HashMap<>();

+      error.put("error", e.getMessage());

+      result = generateFailedResult(error);

+

+      logger.info(logPrefix + "There was an error calling the test head.");

+    }

+

+    testExecution.getTestHeadResults().add(result);

+    saveResult(testExecution);

+    return result;

+  }

+

+  private void setApiKeyIfEnabled(){

+    if(this.testHead.getAuthorizationEnabled() != null && this.testHead.getAuthorizationEnabled().booleanValue()){

+      this.headers.put(HttpHeaders.AUTHORIZATION, testHead.getAuthorizationType() + " " + testHead.getAuthorizationCredential());

+    }

+  }

+

+  private String generateUrl() {

+    String resource = testHead.getResourcePath();

+    // Prepend a forward-slash if the resource path exists, and does NOT already start with one. The

+    // order of this condition is relevant for null-checks.

+    if (!Strings.isNullOrEmpty(resource) && !resource.startsWith("/")) {

+      resource = "/" + resource;

+    }

+    return testHead.getHostname() + ":" + testHead.getPort() + resource;

+  }

+

+  private TestHeadResult generateFailedResult(Map<String, Object> error) {

+    int statusCodeError = -1;

+    TestHeadRequest requestContent = new TestHeadRequest(HeadersUtility.maskAuth(headers), body);

+

+    return new TestHeadResult(

+        testHead.get_id(), testHead.getTestHeadName(), testHead.getGroupId(), activityId, statusCodeError, requestContent, error, startTime, endTime);

+  }

+

+  private TestHeadResult generateResult(HttpResponse response) throws IOException {

+    String sRes = EntityUtils.toString(response.getEntity());

+    JsonParser parser = new JsonParser();

+    Map<String, Object> res;

+    try {

+      res = Convert.jsonToMap(sRes);

+    } catch (Exception e) {

+      res = new HashMap<>();

+      res.put("response", sRes);

+    }

+

+    TestHeadRequest requestContent = new TestHeadRequest(HeadersUtility.maskAuth(headers), body);

+

+    return new TestHeadResult(

+        testHead.get_id(), testHead.getTestHeadName(), testHead.getGroupId(), activityId, response.getStatusLine().getStatusCode(), requestContent, res, startTime, endTime);

+  }

+

+  private TestHeadResult generateResult(Map<String, Object> res) {

+

+    //TODO: This will need to change if enhancement is made to allow status codes

+    TestHeadRequest requestContent = new TestHeadRequest(HeadersUtility.maskAuth(headers), body);

+

+    return new TestHeadResult(

+        testHead.get_id(), testHead.getTestHeadName(), testHead.getGroupId(), activityId, 200, requestContent, res, startTime, endTime);

+  }

+

+  private void saveResult(TestExecution testExecution) {

+    Query query = new Query();

+    query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));

+    // Also add businessKey as a criteria because the object won't be found if the business key

+    // was somehow modified in the workflow.

+    query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));

+    Update update = new Update();

+    update.set("testHeadResults", testExecution.getTestHeadResults());

+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+    // Check the status of the findAndUpdate database, and appropriately handle the errors.

+    if (result.getMatchedCount() == 0) {

+      throw new TestExecutionException(

+          String.format(

+              "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",

+              testExecution.get_id(), testExecution.getBusinessKey()));

+    } else if (result.getModifiedCount() == 0) {

+      throw new TestExecutionException("Unable to persist the testExecution to the database.");

+    }

+  }

+

+  private Map<String, Object> simulateVTH(String activityId, Map<String, Object> simulationVth) {

+    int delay = 0;

+    Map response = new HashMap<String, Object>();

+    if (simulationVth.containsKey(activityId)) {

+      Object obj = simulationVth.get(activityId);

+      if (obj instanceof Map) {

+        simulationVth = (Map) obj;

+      }

+    } else {

+      return null;

+    }

+

+    if (simulationVth.containsKey("delay")) {

+      Object obj = simulationVth.get("delay");

+      if (obj instanceof Integer) {

+        delay = (int) obj;

+      }

+    }

+

+    if (simulationVth.containsKey("response")) {

+      Object obj = simulationVth.get("response");

+      if (obj instanceof Map) {

+        response = (Map) obj;

+      }

+    }

+    logger.info(logPrefix + "Starting simulated call to test head.");

+

+    try {

+      Thread.sleep(delay);

+    } catch (InterruptedException e) {

+      e.printStackTrace();

+      logger.info(logPrefix + "Error simulating call to test head.");

+      return null;

+    }

+    logger.info(logPrefix + "Finished simulating call to test head.");

+    return response;

+  }

+}

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
index 0000000..b5f673d
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/exception/TestExecutionException.java
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.exception;

+

+public class TestExecutionException extends RuntimeException {

+

+  public TestExecutionException(String message) {

+    super(message);

+  }

+

+  public TestExecutionException(String message, Throwable cause) {

+    super(message, cause);

+  }

+

+  public TestExecutionException(Throwable cause) {

+    super(cause);

+  }

+}

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
index 0000000..605c6b0
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/exception/WorkflowException.java
@@ -0,0 +1,95 @@
+/*-

+ * ============LICENSE_START=======================================================

+ * ONAP - SO

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.

+ * ================================================================================

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ * ============LICENSE_END=========================================================

+ */

+

+package org.oran.otf.camunda.exception;

+

+import java.io.Serializable;

+

+/**

+ * An object that represents a workflow exception.

+ */

+public class WorkflowException implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private final String processKey;

+  private final int errorCode;

+  private final String errorMessage;

+  private final String workStep;

+

+  /**

+   * Constructor

+   *

+   * @param processKey the process key for the process that generated the exception

+   * @param errorCode the numeric error code (normally 1xxx or greater)

+   * @param errorMessage a short error message

+   */

+  public WorkflowException(String processKey, int errorCode, String errorMessage) {

+    this.processKey = processKey;

+    this.errorCode = errorCode;

+    this.errorMessage = errorMessage;

+    workStep = "*";

+  }

+

+  public WorkflowException(String processKey, int errorCode, String errorMessage, String workStep) {

+    this.processKey = processKey;

+    this.errorCode = errorCode;

+    this.errorMessage = errorMessage;

+    this.workStep = workStep;

+  }

+

+  /**

+   * Returns the process key.

+   */

+  public String getProcessKey() {

+    return processKey;

+  }

+

+  /**

+   * Returns the error code.

+   */

+  public int getErrorCode() {

+    return errorCode;

+  }

+

+  /**

+   * Returns the error message.

+   */

+  public String getErrorMessage() {

+    return errorMessage;

+  }

+

+  /**

+   * Returns the error message.

+   */

+  public String getWorkStep() {

+    return workStep;

+  }

+

+  /**

+   * Returns a string representation of this object.

+   */

+  @Override

+  public String toString() {

+    return getClass().getSimpleName() + "[processKey=" + getProcessKey() + ",errorCode="

+        + getErrorCode()

+        + ",errorMessage=" + getErrorMessage() + ",workStep=" + getWorkStep() + "]";

+  }

+}

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
index 0000000..8997656
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/exception/WorkflowProcessorException.java
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.exception;

+

+import org.oran.otf.camunda.model.WorkflowResponse;

+

+public class WorkflowProcessorException extends RuntimeException {

+

+  private WorkflowResponse response;

+

+  public WorkflowProcessorException(WorkflowResponse response) {

+    this.response = response;

+  }

+

+  public WorkflowResponse getWorkflowResponse() {

+    return response;

+  }

+}

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
index 0000000..f886000
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/listener/EndEventListener.java
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.listener;

+

+import org.oran.otf.event.TestInstanceCompletionEvent;

+import com.google.gson.JsonObject;

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.camunda.bpm.engine.delegate.ExecutionListener;

+import org.camunda.bpm.extension.reactor.bus.CamundaSelector;

+import org.camunda.bpm.extension.reactor.spring.listener.ReactorExecutionListener;

+import org.camunda.bpm.model.bpmn.instance.EndEvent;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.context.ApplicationEventPublisher;

+import org.springframework.stereotype.Component;

+

+@Component

+@CamundaSelector(event = ExecutionListener.EVENTNAME_END)

+public class EndEventListener extends ReactorExecutionListener {

+

+  private static Logger LOGGER = LoggerFactory.getLogger(EndEventListener.class);

+

+  @Autowired

+  private ApplicationEventPublisher publisher;

+

+  @Override

+  public void notify(DelegateExecution execution) {

+    JsonObject jmsg = new JsonObject();

+    jmsg.addProperty("executionId", execution.getProcessInstanceId());

+    jmsg.addProperty("origin", "otf-camunda");

+    if (execution.getBpmnModelElementInstance() instanceof EndEvent) {

+      LOGGER.info(execution.getProcessInstanceId() + " is finished.");

+      jmsg.addProperty("status", "completed");

+      publisher.publishEvent(new TestInstanceCompletionEvent(this, jmsg, execution));

+    }

+  }

+

+  private void onEndEvent(DelegateExecution execution) {

+

+  }

+

+  private void onVthEnd(DelegateExecution execution) {

+    // Useful for reporting back the result of a VTH

+  }

+}

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
index 0000000..9fa6d14
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/listener/StartEventListener.java
@@ -0,0 +1,97 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.listener;

+

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.model.ExecutionConstants;

+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.utility.Utility;

+import com.google.gson.JsonObject;

+import com.mongodb.client.result.UpdateResult;

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.camunda.bpm.engine.delegate.ExecutionListener;

+import org.camunda.bpm.extension.reactor.bus.CamundaSelector;

+import org.camunda.bpm.extension.reactor.spring.listener.ReactorExecutionListener;

+import org.camunda.bpm.model.bpmn.instance.StartEvent;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+import org.springframework.stereotype.Component;

+

+@Component

+@CamundaSelector(event = ExecutionListener.EVENTNAME_END)

+public class StartEventListener extends ReactorExecutionListener {

+

+  @Autowired

+  WorkflowUtility utility;

+

+  @Autowired

+  MongoTemplate mongoOperation;

+

+  private static Logger LOGGER = LoggerFactory.getLogger(StartEventListener.class);

+

+  @Override

+  public void notify(DelegateExecution execution) {

+    if (execution.getBpmnModelElementInstance() instanceof StartEvent) {

+      LOGGER.info(execution.getProcessInstanceId() + " has started.");

+      //setTestResult(execution, ExecutionConstants.TestResult.STARTED);

+    }

+  }

+

+  private void onStartEvent(DelegateExecution execution) {

+  }

+

+  private void onVthStart(DelegateExecution execution) {

+    // Useful for reporting back the exact parameters being sent to the VTH as they can be modified

+    // in the workflow

+  }

+

+  private void setTestResult(DelegateExecution execution, String result){

+    // Get the current test execution object.

+    final String logPrefix = Utility.getLoggerPrefix();

+

+    TestExecution testExecution =

+            utility.getExecutionVariable(

+                    execution.getVariables(), ExecutionConstants.ExecutionVariable.TEST_EXECUTION, TestExecution.class);

+    // Perform a null-check to ensure it is available. It's critical to throw an exception if it

+    // is not available since the object is essential for results.

+    if (testExecution == null) {

+      LOGGER.error(logPrefix + "Test execution is null.");

+      throw new TestExecutionException("The test execution was not found.");

+    }

+    execution.setVariable(ExecutionConstants.ExecutionVariable.TEST_RESULT, result);

+

+    testExecution.setTestResult(result);

+    testExecution.setProcessInstanceId(execution.getProcessInstanceId());

+

+

+    Query query = new Query();

+    query.addCriteria(Criteria.where("businessKey").is(execution.getProcessBusinessKey()));

+    Update update = new Update();

+    update.set("testResult", testExecution.getTestResult());

+    update.set("processInstanceId", execution.getProcessInstanceId());

+    UpdateResult updateResult = mongoOperation.updateFirst(query, update, TestExecution.class);

+

+  }

+

+

+}

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
index 0000000..37ca685
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/listener/TaskEndEventListener.java
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.listener;

+

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;

+import org.oran.otf.common.utility.Utility;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.camunda.bpm.engine.delegate.ExecutionListener;

+import org.camunda.bpm.engine.impl.context.Context;

+import org.camunda.bpm.engine.impl.interceptor.Command;

+import org.camunda.bpm.engine.impl.interceptor.CommandContext;

+import org.camunda.bpm.engine.runtime.ProcessInstance;

+import org.camunda.bpm.extension.reactor.bus.CamundaSelector;

+import org.camunda.bpm.extension.reactor.spring.listener.ReactorExecutionListener;

+import org.camunda.bpm.model.bpmn.instance.Task;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.stereotype.Component;

+

+@Component

+@CamundaSelector(event = ExecutionListener.EVENTNAME_END)

+public class TaskEndEventListener extends ReactorExecutionListener {

+

+    @Autowired

+    WorkflowUtility utility;

+

+    @Autowired

+    MongoTemplate mongoOperation;

+

+    @Autowired

+    RuntimeService runtimeService;

+

+    private static Logger LOGGER = LoggerFactory.getLogger(TaskEndEventListener.class);

+

+    @Override

+    public void notify(DelegateExecution execution) {

+        if(execution.getBpmnModelElementInstance() instanceof Task){

+            String processInstanceId = execution.getProcessInstanceId();

+            ProcessInstance processInstance;

+            try {

+                processInstance = checkProcessInstanceStatus(processInstanceId);

+            }catch(Exception e){

+               throw new TestExecutionException("Error trying to obtain process instance status, error: " + e) ;

+            }

+            // if process instance not found then terminate the current process

+            if(processInstance == null || processInstance.isEnded() || processInstance.isSuspended()){

+                String logPrefix = Utility.getLoggerPrefix();

+

+                LOGGER.info(logPrefix + "Process Instance not found. Terminating current job (thread).");

+                Thread.currentThread().interrupt();

+                throw new TestExecutionException("Terminated Process Instance: " + processInstanceId + ". Process Instance no longer exists, thread has been forcefully interrupted");

+            }

+        }

+    }

+

+    private ProcessInstance checkProcessInstanceStatus(String processInstanceId){

+        return Context.getProcessEngineConfiguration().getCommandExecutorTxRequiresNew().execute(new Command<ProcessInstance>() {

+            @Override

+            public ProcessInstance execute(CommandContext commandContext){

+                return runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();

+            }

+        });

+    }

+

+}

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
index 0000000..d266457
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/model/ExecutionConstants.java
@@ -0,0 +1,56 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.model;

+

+public class ExecutionConstants {

+  public class TestResult {

+    public static final String STARTED = "STARTED";

+    public static final String COMPLETED = "COMPLETED";

+    //remore redundent test results

+//    public static final String FAILURE = "FAILURE";

+    public static final String FAILED = "FAILED";

+    public static final String STOPPED = "STOPPED";

+    public static final String SUCCESS = "SUCCESS";

+    public static final String TERMINATED = "TERMINATED";

+    public static final String UNAUTHORIZED = "UNAUTHORIZED";

+    public static final String DOES_NOT_EXIST = "DOES_NOT_EXIST";

+    public static final String UNKNOWN = "UNKNOWN";

+    // error can be assignned in a workflow. if user uses workflow error reassign to error

+    public static final String ERROR = "ERROR";

+    // workflow error is only used for exceptions and bugs

+    public static final String WORKFLOW_ERROR = "WORKFLOW_ERROR";

+    public static final String OTHER = "OTHER";

+  }

+

+  public class ExecutionVariable {

+    public static final String TEST_EXECUTION = "otf-execution-testExecution";

+    public static final String TEST_EXECUTION_ENCRYPTED = "otf-execution-encrypted";

+    public static final String PFLO_INPUT = "pfloInput";

+    public static final String TEST_DATA = "testData";

+    public static final String TEST_DETAILS = "testDetails";

+    public static final String TEST_RESULT = "testResult";

+    public static final String VTH_INPUT = "vthInput";

+    public static final String TEST_RESULT_MESSAGE = "testResultMessage";

+  }

+

+  public static String [] getAllTestResultStr(){

+      return new String[] {TestResult.STARTED,TestResult.COMPLETED,TestResult.FAILED,

+              TestResult.STOPPED,TestResult.SUCCESS,TestResult.TERMINATED,

+              TestResult.UNAUTHORIZED,TestResult.DOES_NOT_EXIST,TestResult.UNKNOWN,

+              TestResult.ERROR,TestResult.OTHER};

+  }

+}

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
index 0000000..ea9fc28
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/model/WorkflowResponse.java
@@ -0,0 +1,88 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.model;

+

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.utility.gson.Convert;

+

+import java.util.Map;

+

+/**

+ * @version 1.0 Synchronous workflow response bean

+ */

+public class WorkflowResponse {

+

+  private String response;

+  private String message;

+  private String processInstanceId;

+  private Map<String, String> variables;

+  private TestExecution testExecution;

+  private int messageCode;

+

+  public String getResponse() {

+    return response;

+  }

+

+  public void setResponse(String response) {

+    this.response = response;

+  }

+

+  public String getMessage() {

+    return message;

+  }

+

+  public void setMessage(String message) {

+    this.message = message;

+  }

+

+  public String getProcessInstanceId() {

+    return processInstanceId;

+  }

+

+  public void setProcessInstanceId(String pID) {

+    this.processInstanceId = pID;

+  }

+

+  public Map<String, String> getVariables() {

+    return variables;

+  }

+

+  public void setVariables(Map<String, String> variables) {

+    this.variables = variables;

+  }

+

+  public int getMessageCode() {

+    return messageCode;

+  }

+

+  public void setMessageCode(int messageCode) {

+    this.messageCode = messageCode;

+  }

+

+  public TestExecution getTestExecution() {

+    return testExecution;

+  }

+

+  public void setTestExecution(TestExecution testExecution) {

+    this.testExecution = testExecution;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..6aceed4
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/plugin/OtfIncidentHandlerPlugin.java
@@ -0,0 +1,54 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.plugin;

+

+import org.oran.otf.camunda.workflow.handler.ExternalTaskIncidentHandler;

+import org.oran.otf.camunda.workflow.handler.FailedJobIncidentHandler;

+import java.util.Arrays;

+import org.camunda.bpm.engine.ProcessEngine;

+import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;

+import org.camunda.bpm.engine.impl.cfg.ProcessEnginePlugin;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Component;

+

+@Component

+public class OtfIncidentHandlerPlugin implements ProcessEnginePlugin {

+

+  private static final Logger logger = LoggerFactory.getLogger(OtfIncidentHandlerPlugin.class);

+

+  @Autowired

+  private FailedJobIncidentHandler failedJobIncidentHandler;

+  @Autowired

+  private ExternalTaskIncidentHandler externalTaskIncidentHandler;

+

+  @Override

+  public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {

+    logger.info("Adding Open Test Framework custom incident handlers.");

+    processEngineConfiguration.setCustomIncidentHandlers(

+        Arrays.asList(failedJobIncidentHandler, externalTaskIncidentHandler));

+  }

+

+  @Override

+  public void postInit(ProcessEngineConfigurationImpl processEngineConfiguration) {

+  }

+

+  @Override

+  public void postProcessEngineBuild(ProcessEngine processEngine) {

+  }

+}

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
index 0000000..c414528
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/service/CamundaShutdown.java
@@ -0,0 +1,143 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.service;

+

+import static org.springframework.data.mongodb.core.query.Criteria.where;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.camunda.model.ExecutionConstants.TestResult;

+import org.oran.otf.camunda.workflow.utility.WorkflowTask;

+import org.oran.otf.common.model.TestExecution;

+

+import org.oran.otf.service.impl.DeveloperServiceImpl;

+import java.util.ArrayList;

+import java.util.HashSet;

+import java.util.Iterator;

+import java.util.List;

+import java.util.Set;

+

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.OptimisticLockingException;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.runtime.ProcessInstance;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.data.mongodb.core.BulkOperations;

+import org.springframework.data.mongodb.core.BulkOperations.BulkMode;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+import org.springframework.stereotype.Component;

+

+@Component

+public class CamundaShutdown {

+

+  private Logger logger = LoggerFactory.getLogger(DeveloperServiceImpl.class);

+

+  @Autowired

+  private MongoTemplate mongoTemplate;

+

+  public CamundaShutdown(){}

+

+  //TODO: delete unused code

+  public Set<String> gracefulShutdown(){

+    Set<String> processIds = new HashSet<>();

+

+    try {

+      if (!WorkflowTask.workflowTasksByExecutionId.isEmpty()) {

+        processIds = WorkflowTask.workflowTasksByExecutionId.keySet();

+        if (processIds != null) {

+          suspendTasks(processIds);

+          //1. Update processes running as TERMINATED

+          BulkOperations updates = prepareBatchUpdate(processIds);

+          updates.execute();

+

+          //3.kill poolthreads

+          processIds = this.shutdownAllProcessThreads(processIds);

+          //this.shutdownAllProcessThreads(processIds);

+

+          //2.look up process instances and delete the suspeded processes

+          processIds = queryProcessInstances(processIds);

+

+        }

+      }

+    }catch (OptimisticLockingException e){

+      //4. Update processes running as TERMINATED

+      BulkOperations threadsInterrupted = prepareBatchUpdate(processIds);

+      threadsInterrupted.execute();

+      logger.info("Optimistic error was caught by graceful shutdown method");

+    }

+    return processIds;

+  }

+  private void suspendTasks(Set<String> processIds){

+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(

+        OtfCamundaConfiguration.processEngineName).getRuntimeService();

+    for(String id: processIds){

+      runtimeService.suspendProcessInstanceById(id);

+    }

+  }

+

+    private Set<String> queryProcessInstances(Set<String> processIds){

+      RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(

+          OtfCamundaConfiguration.processEngineName).getRuntimeService();

+      for(String id: processIds){

+        ProcessInstance instance = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult();

+        if(instance == null || instance.isEnded()){

+          processIds.remove(id);

+        }

+      }

+      List<String> del = new ArrayList<>(processIds);

+      runtimeService.deleteProcessInstances(del, "Camunda Shutting down, proccess forcefully terminated", false, false , false);

+      return processIds;

+

+    }

+

+  private Set<String> shutdownAllProcessThreads(Set<String> processIds){

+    Set<String> terminatedProcesses = new HashSet<>();

+    Iterator processes = processIds.iterator();

+    //Iterator processes = WorkflowTask.workflowTasksByExecutionId.entrySet().iterator();

+    while(processes.hasNext()){

+      Object processHolder = processes.next();

+      List<WorkflowTask> tasks = WorkflowTask.workflowTasksByExecutionId.get(processHolder.toString());

+      //List<WorkflowTask> tasks = WorkflowTask.workflowTasksByExecutionId.get(processes.next());

+      if(tasks != null){

+        terminatedProcesses.add(processHolder.toString());

+        for(WorkflowTask task: tasks){

+          task.shutdown(true);

+        }

+      }

+

+      else{

+        //processIds.remove(processes.next());

+      }

+    }

+    return terminatedProcesses;

+  }

+  private BulkOperations prepareBatchUpdate(Set<String> processIds){

+    //Set<String> processInstanceIds = this.runningProcessInstanceIds();

+    Iterator<String> ids = processIds.iterator();//processInstanceIds.iterator();

+    BulkOperations bulkOperations = mongoTemplate.bulkOps(BulkMode.ORDERED, TestExecution.class);

+    while(ids.hasNext()){

+      ids.hasNext();

+      //Get tasks by processInstanceId

+      Update update = new Update().set("testResult", TestResult.TERMINATED).set("testResultMessage", "Camunda application had to shutdown for maintenance, Test execution was TERMINATED");

+      bulkOperations.updateOne(Query.query(where("processInstanceId").is(ids.next())), update);

+    }

+    return bulkOperations;

+  }

+}

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
index 0000000..c23d1cb
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/service/OtfExternalTaskService.java
@@ -0,0 +1,195 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.service;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.camunda.delegate.otf.common.CallTestHeadDelegate;

+import org.oran.otf.camunda.delegate.otf.common.RunTestInstanceDelegate;

+import com.google.common.util.concurrent.ThreadFactoryBuilder;

+import java.util.ArrayList;

+import java.util.List;

+import java.util.concurrent.ThreadFactory;

+import java.util.concurrent.ThreadLocalRandom;

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.ExternalTaskService;

+import org.camunda.bpm.engine.externaltask.LockedExternalTask;

+import org.camunda.bpm.engine.variable.VariableMap;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.context.event.ApplicationReadyEvent;

+import org.springframework.context.event.EventListener;

+import org.springframework.stereotype.Component;

+

+@Component

+public class OtfExternalTaskService {

+

+  private static Logger logger = LoggerFactory.getLogger(OtfExternalTaskService.class);

+  public static boolean isEnabled;

+  private static long pollIntervalInMillis = 1000;

+  @Autowired CallTestHeadDelegate callTestHeadDelegate;

+  @Autowired RunTestInstanceDelegate runTestInstanceDelegate;

+  private ExternalTaskService externalTaskService;

+

+  private List<LockedExternalTask> externalTasks;

+

+  @Value("${otf.camunda.executors-active}")

+  private boolean executorsActive;

+

+  @EventListener(ApplicationReadyEvent.class)

+  public void initialize() {

+    this.externalTaskService =

+        BpmPlatform.getProcessEngineService()

+            .getProcessEngine(OtfCamundaConfiguration.processEngineName)

+            .getExternalTaskService();

+

+    pollIntervalInMillis = ThreadLocalRandom.current().nextLong(500, 5000);

+    //    this.externalTaskService =

+    //        BpmPlatform.getProcessEngineService()

+    //            .getProcessEngine(OtfCamundaConfiguration.processEngineName)

+    //            .getExternalTaskService();

+

+    logger.info(

+        "Initializing external task service with poll interval at {}", pollIntervalInMillis);

+    externalTasks = new ArrayList<>();

+    isEnabled = this.executorsActive;

+    logger.info("External Task Worker otf.camunda.executors-active set to : "  + this.executorsActive);

+    Thread t =

+        new Thread(

+            () -> {

+              while (true) {

+                try {

+                  if (isEnabled) {

+                    acquire();

+                  }

+

+                  Thread.sleep(pollIntervalInMillis);

+                } catch (Exception e) {

+                  logger.error(e.getMessage());

+                }

+              }

+            });

+

+    t.start();

+  }

+

+  private void acquire() {

+    externalTasks.clear();

+    List<LockedExternalTask> externalTasks =

+        externalTaskService

+            .fetchAndLock(10, "etw_" + OtfCamundaConfiguration.processEngineName)

+            .topic("vth", 43200000)

+            .enableCustomObjectDeserialization()

+            .topic("testInstance", 43200000)

+            .enableCustomObjectDeserialization()

+            .execute();

+    externalTasks.forEach(this::handleExternalTask);

+  }

+

+  private void handleExternalTask(LockedExternalTask task) {

+    logger.info("[" + task.getId() + "]: Handling external task for topic: " + task.getTopicName());

+    String topicName = task.getTopicName();

+    ExternalTaskCallable callable;

+

+    // Set retries to 0 for the current task.

+    // externalTaskService.setRetries(task.getId(), 0);

+

+    switch (topicName) {

+      case "vth":

+        callable = new ExternalTaskCallable(task, OtfExternalTask.VTH);

+        break;

+      case "testInstance":

+        callable = new ExternalTaskCallable(task, OtfExternalTask.TEST_INSTANCE);

+        break;

+      default:

+        String err = String.format("The topic name %s has no external task handler.", topicName);

+        logger.error(err);

+        externalTaskService.handleFailure(task.getId(), task.getWorkerId(), err, 0, 0);

+        return;

+    }

+

+    try {

+      ThreadFactory namedThreadFactory =

+          new ThreadFactoryBuilder().setNameFormat("etw-" + task.getTopicName() + "-%d").build();

+      namedThreadFactory.newThread(callable).start();

+    } catch (Exception e) {

+      externalTaskService.handleFailure(

+          task.getId(), task.getWorkerId(), e.getMessage(), e.toString(), 0, 0);

+    }

+  }

+

+  public enum OtfExternalTask {

+    VTH,

+    TEST_INSTANCE

+  }

+

+  public class ExternalTaskCallable implements Runnable {

+

+    private final LockedExternalTask task;

+    private final OtfExternalTask type;

+

+    private final String activityId;

+    private final String processDefinitionId;

+    private final String processInstanceId;

+    private final String processBusinessKey;

+    private VariableMap variables;

+

+    private ExternalTaskCallable(LockedExternalTask lockedExternalTask, OtfExternalTask type) {

+      this.task = lockedExternalTask;

+      this.type = type;

+

+      this.activityId = task.getActivityId();

+      this.processDefinitionId = task.getProcessDefinitionId();

+      this.processInstanceId = task.getProcessInstanceId();

+      this.processBusinessKey = task.getBusinessKey();

+      this.variables = task.getVariables();

+    }

+

+    @Override

+    public void run() {

+      try {

+        if (type == OtfExternalTask.VTH) {

+          callTestHeadDelegate.callTestHead(

+              activityId, processDefinitionId, processInstanceId, processBusinessKey, variables);

+        } else if (type == OtfExternalTask.TEST_INSTANCE) {

+          runTestInstanceDelegate.runTestInstance(activityId, processInstanceId, variables);

+        } else {

+          logger.error(

+              String.format(

+                  "Could not find the appropriate function for external task with id %s.", type));

+        }

+      } catch (Exception e) {

+        String err = String.format("Encountered error %s", e.getMessage());

+        externalTaskService.handleFailure(

+            task.getId(), task.getWorkerId(), e.getMessage(), err, 0, 0);

+        return;

+      }

+

+      synchronized (externalTaskService) {

+        try {

+          externalTaskService.complete(task.getId(), task.getWorkerId(), variables);

+        } catch (Exception e) {

+          String err = String.format("Encountered error %s", e.getMessage());

+          e.printStackTrace();

+          externalTaskService.handleFailure(

+                  task.getId(), task.getWorkerId(), e.getMessage(), err, 0, 0);

+        }

+      }

+    }

+  }

+}

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
index 0000000..ca9d7a8
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/service/OtfWorkflowTaskCleanupService.java
@@ -0,0 +1,75 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.service;

+

+import org.oran.otf.camunda.workflow.utility.WorkflowTask;

+import java.util.List;

+import java.util.Map.Entry;

+import java.util.Set;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.runtime.ProcessInstance;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.boot.context.event.ApplicationReadyEvent;

+import org.springframework.context.event.EventListener;

+import org.springframework.stereotype.Component;

+

+@Component

+public class OtfWorkflowTaskCleanupService {

+  @Autowired RuntimeService runtimeService;

+  public static boolean isEnabled = false;

+

+  @EventListener(ApplicationReadyEvent.class)

+  public void init() {

+    Thread otfCleanupService = new Thread(new Worker());

+    otfCleanupService.start();

+  }

+

+  public class Worker implements Runnable {

+    @Override

+    public void run() {

+      try {

+        while (true) {

+          if (isEnabled) {

+            synchronized (WorkflowTask.workflowTasksByExecutionId) {

+              Set<Entry<String, List<WorkflowTask>>> set =

+                  WorkflowTask.workflowTasksByExecutionId.entrySet();

+

+              for (Entry<String, List<WorkflowTask>> entry : set) {

+                String processInstanceId = entry.getKey();

+                List<WorkflowTask> workflowTasks = entry.getValue();

+

+                ProcessInstance processInstance =

+                    runtimeService

+                        .createProcessInstanceQuery()

+                        .processInstanceId(processInstanceId)

+                        .singleResult();

+

+                if (processInstance == null) {

+                  System.out.println("Cleaning up WorkflowTasks under processInstanceId, " + processInstanceId);

+                  workflowTasks.forEach(WorkflowTask::shutdown);

+                }

+              }

+            }

+          }

+          Thread.sleep(10000);

+        }

+      } catch (InterruptedException e) {

+        e.printStackTrace();

+      }

+    }

+  }

+}

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
index 0000000..6c8215b
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/service/ProcessEngineAwareService.java
@@ -0,0 +1,68 @@
+/*-

+ * ============LICENSE_START=======================================================

+ * ONAP - SO

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.

+ * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.

+ * ================================================================================

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ * ============LICENSE_END=========================================================

+ */

+

+package org.oran.otf.camunda.service;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import java.util.Optional;

+import org.camunda.bpm.engine.ProcessEngineServices;

+import org.camunda.bpm.engine.ProcessEngines;

+import org.springframework.stereotype.Service;

+

+/**

+ * Base class for services that must be process-engine aware. The only process engine currently

+ * supported is the "default" process engine.

+ */

+@Service

+public class ProcessEngineAwareService {

+

+  //  private final String processEngineName = OTFProcessEngineConfiguration.processEngineName;

+  private final String processEngineName = OtfCamundaConfiguration.processEngineName;

+  private volatile Optional<ProcessEngineServices> pes4junit = Optional.empty();

+

+  /**

+   * Gets the process engine name.

+   *

+   * @return the process engine name

+   */

+  public String getProcessEngineName() {

+    return processEngineName;

+  }

+

+  /**

+   * Gets process engine services.

+   *

+   * @return process engine services

+   */

+  public ProcessEngineServices getProcessEngineServices() {

+    return pes4junit.orElse(ProcessEngines.getProcessEngine(getProcessEngineName()));

+  }

+

+  /**

+   * Allows a particular process engine to be specified, overriding the usual process engine lookup

+   * by name. Intended primarily for the unit test environment.

+   *

+   * @param pes process engine services

+   */

+  public void setProcessEngineServices4junit(ProcessEngineServices pes) {

+    pes4junit = Optional.ofNullable(pes);

+  }

+}

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
index 0000000..10a1dfd
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/WorkflowProcessor.java
@@ -0,0 +1,526 @@
+/*-

+ * ============LICENSE_START=======================================================

+ * ONAP - SO

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.

+ * ================================================================================

+ * Modifications Copyright (c) 2019 Samsung

+ * ================================================================================

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ * ============LICENSE_END=========================================================

+ */

+

+package org.oran.otf.camunda.workflow;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.exception.WorkflowProcessorException;

+import org.oran.otf.camunda.model.ExecutionConstants.ExecutionVariable;

+import org.oran.otf.camunda.model.ExecutionConstants.TestResult;

+import org.oran.otf.camunda.model.WorkflowResponse;

+import org.oran.otf.camunda.service.ProcessEngineAwareService;

+import org.oran.otf.camunda.workflow.utility.WorkflowUtility;

+import org.oran.otf.common.model.*;

+import org.oran.otf.common.model.historic.TestDefinitionHistoric;

+import org.oran.otf.common.model.historic.TestInstanceHistoric;

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.model.local.ParallelFlowInput;

+import org.oran.otf.common.repository.*;

+import org.oran.otf.common.utility.Utility;

+import org.oran.otf.common.utility.database.Generic;

+import org.oran.otf.common.utility.permissions.PermissionChecker;

+import org.oran.otf.common.utility.permissions.UserPermission;

+import com.mongodb.client.result.UpdateResult;

+import java.util.ArrayList;

+import java.util.Date;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.Objects;

+import java.util.UUID;

+import org.bson.types.ObjectId;

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.RepositoryService;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.repository.ProcessDefinition;

+import org.camunda.bpm.engine.runtime.ProcessInstance;

+import org.camunda.bpm.engine.variable.VariableMap;

+import org.camunda.bpm.engine.variable.Variables;

+import org.camunda.bpm.engine.variable.impl.VariableMapImpl;

+import org.oran.otf.common.model.*;

+import org.oran.otf.common.repository.*;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.boot.context.event.ApplicationReadyEvent;

+import org.springframework.context.event.EventListener;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+import org.springframework.stereotype.Component;

+

+@Component

+public class WorkflowProcessor extends ProcessEngineAwareService {

+

+    private static final String logPrefix = Utility.getLoggerPrefix();

+    private static final Logger logger = LoggerFactory.getLogger(WorkflowProcessor.class);

+

+    @Autowired

+    GroupRepository groupRepository;

+    @Autowired

+    TestDefinitionRepository testDefinitionRepository;

+    @Autowired

+    TestInstanceRepository testInstanceRepository;

+    @Autowired

+    UserRepository userRepository;

+    @Autowired

+    TestExecutionRepository testExecutionRepository;

+    @Autowired

+    MongoTemplate mongoOperation;

+    @Autowired

+    WorkflowUtility workflowUtility;

+

+    private RuntimeService runtimeService;

+    private RepositoryService repositoryService;

+

+    // Note: the business key is used to identify the process in unit tests

+    protected static String getBusinessKey(Map<String, Object> inputVariables) {

+        return getOrCreate(inputVariables, "otf-business-key");

+    }

+

+    protected static Map<String, Object> getInputVariables(VariableMapImpl variableMap) {

+        Map<String, Object> inputVariables = new HashMap<>();

+        @SuppressWarnings("unchecked")

+        Map<String, Object> vMap = (Map<String, Object>) variableMap.get("variables");

+        for (Map.Entry<String, Object> entry : vMap.entrySet()) {

+            String vName = entry.getKey();

+            Object value = entry.getValue();

+            @SuppressWarnings("unchecked")

+            Map<String, Object> valueMap = (Map<String, Object>) value; // value, type

+            inputVariables.put(vName, valueMap.get("value"));

+        }

+        return inputVariables;

+    }

+

+    protected static String getOrCreate(Map<String, Object> inputVariables, String key) {

+        String value = Objects.toString(inputVariables.get(key), null);

+        if (value == null) {

+            value = UUID.randomUUID().toString();

+            inputVariables.put(key, value);

+        }

+        return value;

+    }

+

+    private static void buildVariable(

+        String key, String value, Map<String, Object> variableValueType) {

+        Map<String, Object> host = new HashMap<>();

+        host.put("value", value);

+        host.put("type", "String");

+        variableValueType.put(key, host);

+    }

+

+    @EventListener(ApplicationReadyEvent.class)

+    private void initialize() {

+        if (this.runtimeService == null) {

+            this.runtimeService =

+                BpmPlatform.getProcessEngineService()

+                    .getProcessEngine(OtfCamundaConfiguration.processEngineName)

+                    .getRuntimeService();

+        }

+        if (this.repositoryService == null) {

+            this.repositoryService =

+                BpmPlatform.getProcessEngineService()

+                    .getProcessEngine(OtfCamundaConfiguration.processEngineName)

+                    .getRepositoryService();

+        }

+    }

+

+    public TestExecution processWorkflowRequest(WorkflowRequest request)

+        throws WorkflowProcessorException {

+

+        // Check if the test instance exists.

+        TestInstance testInstance =

+            Generic.findByIdGeneric(testInstanceRepository, request.getTestInstanceId());

+        if (testInstance == null) {

+            WorkflowResponse response = new WorkflowResponse();

+            response.setMessage(

+                String.format(

+                    "Test instance with identifier %s was not found.",

+                    request.getTestInstanceId().toString()));

+            response.setMessageCode(404);

+            response.setResponse("Unable to start the test instance.");

+            TestExecution testExecution = generateTestExecution(request, null, null, null);

+            testExecution.setTestResult(TestResult.DOES_NOT_EXIST);

+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));

+            response.setTestExecution(testExecution);

+            throw new WorkflowProcessorException(response);

+        }

+

+        // Override the test data and vth input of the instance if the request contains the data.

+        Map<String, Object> vthInput =

+                request.getVthInput() == null ? testInstance.getVthInput() : request.getVthInput();

+        Map<String, Object> testData =

+                request.getTestData() == null ? testInstance.getTestData() : request.getTestData();

+        Map<String, ParallelFlowInput> plfoInput =

+                request.getPfloInput() == null ? testInstance.getPfloInput() : request.getPfloInput();

+

+        testInstance.setVthInput((HashMap<String, Object>) vthInput);

+        testInstance.setTestData((HashMap<String, Object>) testData);

+        testInstance.setPfloInput((HashMap<String, ParallelFlowInput>) plfoInput);

+

+

+        // Check if the test definition linked to the test instance is also present.

+        TestDefinition testDefinition =

+            Generic.findByIdGeneric(testDefinitionRepository, testInstance.getTestDefinitionId());

+        if (testDefinition == null) {

+            WorkflowResponse response = new WorkflowResponse();

+            response.setMessage(

+                String.format(

+                    "Test definition with identifier %s was not found.",

+                    testInstance.getTestDefinitionId().toString()));

+            response.setMessageCode(404);

+            response.setResponse("Unable to start the test instance.");

+            TestExecution testExecution = generateTestExecution(request, testInstance, null, null);

+            testExecution.setTestResult(TestResult.DOES_NOT_EXIST);

+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));

+            response.setTestExecution(testExecution);

+            throw new WorkflowProcessorException(response);

+        }

+

+        // is using latest defintion, verify that the processDefinitionId within camunda is present in

+        // the test definition bpmn instance list

+        if (testInstance.isUseLatestTestDefinition()) {

+            String processDefinitionId =

+                findLatestProcessDefinition(testDefinition.getProcessDefinitionKey());

+            boolean isBpmnInstancePresent =

+                verifyIdExistsInTestDefinition(testDefinition, processDefinitionId);

+            if (isBpmnInstancePresent) {

+                testInstance.setProcessDefinitionId(processDefinitionId);

+            } else {

+                WorkflowResponse response = new WorkflowResponse();

+                response.setMessage(

+                    String.format(

+                        "Latest Test Definition does not exist for key %s.",

+                        testDefinition.getProcessDefinitionKey()));

+                response.setMessageCode(404);

+                response.setResponse("Unable to start the test instance.");

+                TestExecution testExecution =

+                    generateTestExecution(request, testInstance, testDefinition, null);

+                testExecution.setTestResult(TestResult.DOES_NOT_EXIST);

+                testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));

+                response.setTestExecution(testExecution);

+                throw new WorkflowProcessorException(response);

+            }

+        }

+

+        // Check if the entity making the request has permission to run the test instance.

+        User executor = Generic.findByIdGeneric(userRepository, request.getExecutorId());

+        if (executor == null) {

+            WorkflowResponse response = new WorkflowResponse();

+            response.setMessage(

+                String

+                    .format("User with id %s was not found.", request.getExecutorId().toString()));

+            response.setMessageCode(404);

+            response.setResponse("Unable to start the test instance.");

+            TestExecution testExecution =

+                generateTestExecution(request, testInstance, testDefinition, null);

+            testExecution.setTestResult(TestResult.DOES_NOT_EXIST);

+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));

+            response.setTestExecution(testExecution);

+            throw new WorkflowProcessorException(response);

+        }

+//        if (!workflowUtility.hasPermission(executor, testInstance)) {

+//            WorkflowResponse response = new WorkflowResponse();

+//            response.setMessage(

+//                String.format(

+//                    "The user with email %s does not have permission to execute test instance with id: %s.",

+//                    executor.getEmail(), testInstance.get_id().toString()));

+//            response.setMessageCode(401);

+//            response.setResponse("Unauthorized to execute the test instance.");

+//            TestExecution testExecution =

+//                generateTestExecution(request, testInstance, testDefinition, executor);

+//            testExecution.setTestResult(TestResult.UNAUTHORIZED);

+//            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));

+//            response.setTestExecution(testExecution);

+//            throw new WorkflowProcessorException(response);

+//        }

+        Group testInstanceGroup = groupRepository.findById(testInstance.getGroupId().toString()).orElse(null);

+        if(testInstanceGroup == null){

+            WorkflowResponse response = new WorkflowResponse();

+            response.setMessage(

+                    String.format("unable to find test instance group. Group id: %s",testInstance.getGroupId().toString()));

+            response.setMessageCode(404);

+            response.setResponse("unable to find test instance group");

+            TestExecution testExecution = generateTestExecution(request,testInstance,testDefinition,executor);

+            testExecution.setTestResult(TestResult.DOES_NOT_EXIST);

+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));

+            response.setTestExecution(testExecution);

+            throw new WorkflowProcessorException(response);

+        }

+        if (!PermissionChecker.hasPermissionTo(executor,testInstanceGroup, UserPermission.Permission.EXECUTE,groupRepository)){

+            WorkflowResponse response = new WorkflowResponse();

+            response.setMessage(

+                    String.format(

+                            "User with email: %s does not have execute permission on test instance group with id: %s",

+                            executor.getEmail(),testInstance.getGroupId().toString()));

+            response.setMessageCode(401);

+            response.setResponse("unauthorized to execute test instance");

+            TestExecution testExecution = generateTestExecution(request,testInstance,testDefinition,executor);

+            testExecution.setTestResult(TestResult.UNAUTHORIZED);

+            testExecution.setTestDetails(generateTestDetailsWithMessage(response.getMessage()));

+            response.setTestExecution(testExecution);

+            throw new WorkflowProcessorException(response);

+        }

+

+        // Generate a testExecution with a historic copy of the test instance, test definition, and the

+        // email of the person executing the test.

+        TestExecution testExecution =

+            generateTestExecution(request, testInstance, testDefinition, executor);

+

+        // Prepare the test details, test result, test execution, and vth input variables for the

+        // process instance.

+        VariableMap variableMap =

+            Variables.createVariables()

+                .putValueTyped(

+                    ExecutionVariable.TEST_DETAILS,

+                    Variables.objectValue(testExecution.getTestDetails()).create())

+                .putValueTyped(

+                    ExecutionVariable.TEST_RESULT,

+                    Variables.objectValue(testExecution.getTestResult()).create())

+                .putValueTyped(

+                    ExecutionVariable.TEST_RESULT_MESSAGE,

+                    Variables.objectValue(testExecution.getTestResultMessage()).create())

+                .putValueTyped(ExecutionVariable.VTH_INPUT,

+                    Variables.objectValue(vthInput).create())

+                .putValueTyped(ExecutionVariable.TEST_DATA,

+                    Variables.objectValue(testData).create())

+                .putValue(

+                    ExecutionVariable.TEST_EXECUTION,

+                    Variables.objectValue(testExecution)

+                        .serializationDataFormat(Variables.SerializationDataFormats.JAVA)

+                        .create())

+                .putValue(

+                    ExecutionVariable.PFLO_INPUT,

+                    Variables.objectValue(plfoInput)

+                        .serializationDataFormat(Variables.SerializationDataFormats.JAVA)

+                        .create());

+

+        if (testInstance.isUseLatestTestDefinition()) {

+            return startProcessByKey(

+                testDefinition.getProcessDefinitionKey(), variableMap, testExecution);

+        } else {

+            return startProcessById(testInstance.getProcessDefinitionId(), variableMap,

+                testExecution);

+        }

+    }

+

+    public TestExecution startProcessByKey(

+        String processKey, Map<String, Object> variableMap, TestExecution testExecution) {

+        try {

+            logger.info(

+                "***OTF startProcessInstanceByKey with processKey: {} and variables: {}",

+                processKey,

+                variableMap);

+

+            // Set the start time as close to the runtime service start function.

+            testExecution.setStartTime(new Date(System.currentTimeMillis()));

+            testExecutionRepository.insert(testExecution);

+

+            ProcessInstance processInstance =

+                runtimeService.startProcessInstanceByKey(

+                    processKey, testExecution.getBusinessKey(), variableMap);

+

+            // Update the test execution object with the processInstanceId after the processInstanceId is

+            // available.

+            testExecution.setProcessInstanceId(processInstance.getProcessInstanceId());

+            Query query = new Query();

+            query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));

+            // Also add businessKey as a criteria because the object won't be found if the business key

+            // was somehow modified in the workflow.

+            query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));

+            Update update = new Update();

+            update.set("processInstanceId", processInstance.getProcessInstanceId());

+            UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+            // Check the status of the findAndUpdate database, and appropriately handle the errors.

+            if (result.getMatchedCount() == 0) {

+                throw new TestExecutionException(

+                    String.format(

+                        "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",

+                        testExecution.get_id(), testExecution.getBusinessKey()));

+            } else if (result.getModifiedCount() == 0) {

+                throw new TestExecutionException(

+                    "Unable to persist the testExecution to the database.");

+            }

+

+            logger.debug(

+                logPrefix

+                    + "Process "

+                    + processKey

+                    + ":"

+                    + processInstance.getProcessInstanceId()

+                    + " "

+                    + (processInstance.isEnded() ? "ENDED" : "RUNNING"));

+        } catch (Exception e) {

+            WorkflowResponse workflowResponse = new WorkflowResponse();

+            workflowResponse.setResponse("Error occurred while executing the process: " + e);

+            workflowResponse.setProcessInstanceId(testExecution.getProcessInstanceId());

+            workflowResponse.setMessageCode(500);

+            workflowResponse.setMessage("Failed to execute test instance: " + e.getMessage());

+            testExecution.setTestResult(TestResult.FAILED);

+            testExecution

+                .setTestDetails(generateTestDetailsWithMessage(workflowResponse.getMessage()));

+            workflowResponse.setTestExecution(testExecution);

+            throw new WorkflowProcessorException(workflowResponse);

+        }

+

+        return testExecution;

+    }

+

+    private TestExecution startProcessById(

+        String processId, Map<String, Object> variableMap, TestExecution testExecution) {

+        try {

+            logger.debug(

+                "***OTF startProcessInstanceById with processId: {} and variables: {}",

+                processId,

+                variableMap);

+

+            // Set the start time as close to the runtime service start function.

+            testExecution.setStartTime(new Date(System.currentTimeMillis()));

+            testExecutionRepository.insert(testExecution);

+

+            ProcessInstance processInstance =

+                runtimeService.startProcessInstanceById(

+                    processId, testExecution.getBusinessKey(), variableMap);

+

+            // Update the test execution object with the processInstanceId after the processInstanceId is

+            // available.

+            testExecution.setProcessInstanceId(processInstance.getProcessInstanceId());

+            Query query = new Query();

+            query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));

+            // Also add businessKey as a criteria because the object won't be found if the business key

+            // was somehow modified in the workflow.

+            query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));

+            Update update = new Update();

+            update.set("processInstanceId", processInstance.getProcessInstanceId());

+            UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+            // Check the status of the findAndUpdate database, and appropriately handle the errors.

+            if (result.getMatchedCount() == 0) {

+                throw new TestExecutionException(

+                    String.format(

+                        "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",

+                        testExecution.get_id(), testExecution.getBusinessKey()));

+            } else if (result.getModifiedCount() == 0) {

+                throw new TestExecutionException(

+                    "Unable to persist the testExecution to the database.");

+            }

+

+            logger.debug(

+                logPrefix

+                    + "Process "

+                    + processInstance.getProcessInstanceId()

+                    + ":"

+                    + processInstance.getProcessInstanceId()

+                    + " "

+                    + (processInstance.isEnded() ? "ENDED" : "RUNNING"));

+        } catch (Exception e) {

+            WorkflowResponse workflowResponse = new WorkflowResponse();

+            workflowResponse.setResponse("Error occurred while executing the process: " + e);

+            workflowResponse.setProcessInstanceId(testExecution.getProcessInstanceId());

+            workflowResponse.setMessageCode(500);

+            workflowResponse.setMessage("Failed to execute test instance: " + e.getMessage());

+            testExecution.setTestResult(TestResult.FAILED);

+            testExecution

+                .setTestDetails(generateTestDetailsWithMessage(workflowResponse.getMessage()));

+            workflowResponse.setTestExecution(testExecution);

+            throw new WorkflowProcessorException(workflowResponse);

+        }

+

+        return testExecution;

+    }

+

+    private TestExecution generateTestExecution(

+        WorkflowRequest request,

+        TestInstance testInstance,

+        TestDefinition testDefinition,

+        User executor) {

+        TestExecution testExecution = new TestExecution();

+        testExecution.set_id(new ObjectId());

+        testExecution.setExecutorId(request.getExecutorId());

+        testExecution.setAsync(request.isAsync());

+        testExecution.setStartTime(null);

+        testExecution.setTestDetails(new HashMap<>());

+        testExecution.setTestResult(TestResult.UNKNOWN);

+        testExecution.setTestResultMessage("");

+        testExecution.setProcessInstanceId(null);

+        testExecution.setBusinessKey(UUID.randomUUID().toString());

+        testExecution.setTestHeadResults(new ArrayList<>());

+        testExecution.setTestInstanceResults(new ArrayList<>());

+        if (testInstance != null) {

+            testExecution.setGroupId(testInstance.getGroupId());

+            TestInstanceHistoric testInstanceHistoric = new TestInstanceHistoric(testInstance);

+            testExecution.setHistoricTestInstance(testInstanceHistoric);

+        }

+        if (testDefinition != null && testInstance != null) {

+            TestDefinitionHistoric testDefinitionHistoric =

+                new TestDefinitionHistoric(testDefinition, testInstance.getProcessDefinitionId());

+            testExecution.setHistoricTestDefinition(testDefinitionHistoric);

+        }

+        if (executor != null) {

+            testExecution.setHistoricEmail(executor.getEmail());

+        }

+        return testExecution;

+    }

+

+    private Map<String, Object> generateTestDetailsWithMessage(String message) {

+        Map<String, Object> map = new HashMap<>();

+        map.put("message", message);

+        return map;

+    }

+

+    private String findLatestProcessDefinition(String processDefinitionKey) {

+        logger.info("Before find process definition key query.");

+        ProcessDefinition definition =

+            repositoryService

+                .createProcessDefinitionQuery()

+                .processDefinitionKey(processDefinitionKey)

+                .latestVersion()

+                .singleResult();

+        logger.info("After find process definition key query.");

+        String processDefinitionId = null;

+        if (definition != null) {

+            processDefinitionId = definition.getId();

+        }

+        return processDefinitionId;

+    }

+

+    private boolean verifyIdExistsInTestDefinition(

+        TestDefinition definition, String processDefinitionId) {

+        if (processDefinitionId == null || definition == null) {

+            return false;

+        }

+

+        List<BpmnInstance> bpmnInstances = definition.getBpmnInstances();

+        BpmnInstance bpmnInstance =

+            bpmnInstances.stream()

+                .filter(

+                    _bpmnInstance -> {

+                        return _bpmnInstance.isDeployed()

+                            && _bpmnInstance.getProcessDefinitionId() != null

+                            && _bpmnInstance.getProcessDefinitionId().equals(processDefinitionId);

+                    })

+                .findFirst()

+                .orElse(null);

+        return bpmnInstance != null;

+    }

+}

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
index 0000000..fa5f10e
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/WorkflowRequest.java
@@ -0,0 +1,182 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.workflow;

+

+import org.oran.otf.common.model.local.ParallelFlowInput;

+import org.oran.otf.common.utility.gson.Convert;

+import com.fasterxml.jackson.annotation.JsonCreator;

+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

+import com.fasterxml.jackson.annotation.JsonProperty;

+import com.fasterxml.jackson.databind.annotation.JsonSerialize;

+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

+import java.io.Serializable;

+import java.util.Map;

+import org.bson.types.ObjectId;

+

+@JsonIgnoreProperties(ignoreUnknown = true)

+public class WorkflowRequest implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private boolean async = false;

+  @JsonSerialize(using = ToStringSerializer.class)

+  private ObjectId executorId = null;

+

+  @JsonSerialize(using = ToStringSerializer.class)

+  private ObjectId testInstanceId = null;

+

+  private Map<String, ParallelFlowInput> pfloInput = null;

+  private Map<String, Object> testData = null;

+  private Map<String, Object> vthInput = null;

+  private long maxExecutionTimeInMillis = 0L;

+

+  public WorkflowRequest() throws Exception {

+    //this.validate();

+  }

+

+  public WorkflowRequest(

+      boolean async,

+      String executorId,

+      String testInstanceId,

+      long maxExecutionTimeInMillis) {

+    this.async = async;

+    this.executorId = new ObjectId(executorId);

+    this.testInstanceId = new ObjectId(testInstanceId);

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+  }

+

+  public WorkflowRequest(

+      boolean async,

+      ObjectId executorId,

+      ObjectId testInstanceId,

+      Map<String, ParallelFlowInput> pfloInput,

+      Map<String, Object> testData,

+      Map<String, Object> vthInput,

+      int maxExecutionTimeInMillis)

+      throws Exception {

+    this.async = async;

+    this.executorId = executorId;

+    this.testInstanceId = testInstanceId;

+    this.pfloInput = pfloInput;

+    this.testData = testData;

+    this.vthInput = vthInput;

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+

+    this.validate();

+  }

+

+  @JsonCreator

+  public WorkflowRequest(

+      @JsonProperty(value = "async", required = false) boolean async,

+      @JsonProperty(value = "executorId", required = true) String executorId,

+      @JsonProperty(value = "testInstanceId", required = true) String testInstanceId,

+      @JsonProperty(value = "pfloInput", required = false) Map<String, ParallelFlowInput> pfloInput,

+      @JsonProperty(value = "testData", required = false) Map<String, Object> testData,

+      @JsonProperty(value = "vthInput", required = false) Map<String, Object> vthInput,

+      @JsonProperty(value = "maxExecutionTimeInMillis", required = false)

+          int maxExecutionTimeInMillis) throws Exception {

+    this.async = async;

+    this.executorId = new ObjectId(executorId);

+    this.testInstanceId = new ObjectId(testInstanceId);

+    this.pfloInput = pfloInput;

+    this.testData = testData;

+    this.vthInput = vthInput;

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+

+    this.validate();

+  }

+

+  private void validate() throws Exception {

+    String missingFieldFormat = "Missing required field %s.";

+//    if (this.executorId == null) {

+//      throw new Exception(String.format(missingFieldFormat, "executorId"));

+//    }

+

+    if (this.testInstanceId == null) {

+      throw new Exception(String.format(missingFieldFormat, "testInstanceId"));

+    }

+

+    if (this.maxExecutionTimeInMillis < 0L) {

+      this.maxExecutionTimeInMillis = 0L;

+    }

+  }

+

+  public boolean isAsync() {

+    return async;

+  }

+

+  public void setAsync(boolean async) {

+    this.async = async;

+  }

+

+  public ObjectId getExecutorId() {

+    return executorId;

+  }

+

+  public void setExecutorId(ObjectId executorId) {

+    this.executorId = executorId;

+  }

+

+  public ObjectId getTestInstanceId() {

+    return testInstanceId;

+  }

+

+  public void setTestInstanceId(String testInstanceId) {

+    this.testInstanceId = new ObjectId(testInstanceId);

+  }

+

+  public void setTestInstanceId(ObjectId testInstanceId) {

+    this.testInstanceId = testInstanceId;

+  }

+

+  public Map<String, ParallelFlowInput> getPfloInput() {

+    return pfloInput;

+  }

+

+  public void setPfloInput(Map<String, ParallelFlowInput> pfloInput) {

+    this.pfloInput = pfloInput;

+  }

+

+  public Map<String, Object> getTestData() {

+    return testData;

+  }

+

+  public void setTestData(Map<String, Object> testData) {

+    this.testData = testData;

+  }

+

+  public Map<String, Object> getVthInput() {

+    return vthInput;

+  }

+

+  public void setVthInput(Map<String, Object> vthInput) {

+    this.vthInput = vthInput;

+  }

+

+  public long getMaxExecutionTimeInMillis() {

+    return maxExecutionTimeInMillis;

+  }

+

+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..0e7d2ca
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/handler/ExternalTaskIncidentHandler.java
@@ -0,0 +1,139 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.workflow.handler;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.model.ExecutionConstants.TestResult;

+import org.oran.otf.camunda.workflow.utility.WorkflowTask;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.repository.TestExecutionRepository;

+import org.oran.otf.common.utility.Utility;

+import org.oran.otf.service.impl.DeleteProcessInstanceServiceImpl;

+import com.mongodb.client.result.UpdateResult;

+

+import java.util.Date;

+import java.util.List;

+

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.impl.incident.IncidentContext;

+import org.camunda.bpm.engine.impl.incident.IncidentHandler;

+import org.camunda.bpm.engine.runtime.Execution;

+import org.camunda.bpm.engine.runtime.Incident;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.boot.context.event.ApplicationReadyEvent;

+import org.springframework.context.event.EventListener;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+import org.springframework.stereotype.Service;

+

+@Service

+public class ExternalTaskIncidentHandler implements IncidentHandler {

+

+  private static final Logger logger = LoggerFactory.getLogger(ExternalTaskIncidentHandler.class);

+  private static final String logPrefix = Utility.getLoggerPrefix();

+

+  @Autowired

+  private TestExecutionRepository testExecutionRepository;

+  @Autowired

+  private MongoTemplate mongoOperation;

+  @Autowired

+  private DeleteProcessInstanceServiceImpl deleteProcessInstanceService;

+

+  @Override

+  public String getIncidentHandlerType() {

+    return Incident.EXTERNAL_TASK_HANDLER_TYPE;

+  }

+

+  @Override

+  public Incident handleIncident(IncidentContext context, String message) {

+    //need to get process instance id from executionid (parent process)

+    String executionId = context.getExecutionId();

+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();

+

+    Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();

+    String processInstanceId = execution.getProcessInstanceId();

+    TestExecution testExecution =

+        testExecutionRepository.findFirstByProcessInstanceId(processInstanceId).orElse(null);

+

+    if (testExecution == null) {

+      String error =

+          String.format(

+              "%sUnable to find testExecution with processInstanceId %s. This process instance will forcefully be terminated to avoid a rogue process.",

+              logPrefix, processInstanceId);

+      logger.error(error);

+      deleteProcessInstanceService.deleteProcessInstanceInternal(processInstanceId, error);

+    } else {

+      if(!testExecution.getTestResult().equals(TestResult.TERMINATED)){

+        updateTestResult(testExecution, TestResult.WORKFLOW_ERROR, message);

+      }

+      deleteProcessInstanceService.deleteProcessInstanceInternal(processInstanceId, message);

+

+      List<WorkflowTask> workflowTasks =

+          WorkflowTask.workflowTasksByExecutionId.getOrDefault(processInstanceId, null);

+

+      if (workflowTasks != null) {

+        logger.debug("Forcefully terminating workflow tasks for processInstanceId: " + processInstanceId);

+        for (WorkflowTask workflowTask : workflowTasks) {

+          workflowTask.shutdown(true);

+        }

+      }

+    }

+

+    return null;

+  }

+

+  private void updateTestResult(TestExecution testExecution, String testResult, String testResultMessage) {

+    // Set the test result

+    testExecution.setTestResult(testResult);

+    testExecution.setTestResultMessage(testResultMessage);

+    Query query = new Query();

+    query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));

+    // Also add businessKey as a criteria because the object won't be found if the business key

+    // was somehow modified in the workflow.

+    query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));

+    Update update = new Update();

+    update.set("testResult", testExecution.getTestResult());

+    update.set("testResultMessage", testExecution.getTestResultMessage());

+    update.set("endTime", new Date(System.currentTimeMillis()));

+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+    // Check the status of the findAndUpdate database, and appropriately handle the errors.

+    if (result.getMatchedCount() == 0) {

+      throw new TestExecutionException(

+          String.format(

+              "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",

+              testExecution.get_id(), testExecution.getBusinessKey()));

+    } else if (result.getModifiedCount() == 0) {

+      throw new TestExecutionException("Unable to persist the testExecution to the database.");

+    }

+  }

+

+  @Override

+  public void resolveIncident(IncidentContext context) {

+    //    logger.info("incident resolved");

+  }

+

+  @Override

+  public void deleteIncident(IncidentContext context) {

+    //    logger.info("incident deleted");

+  }

+}

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
index 0000000..f01d550
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/handler/FailedJobIncidentHandler.java
@@ -0,0 +1,145 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.workflow.handler;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.model.ExecutionConstants.TestResult;

+import org.oran.otf.camunda.workflow.utility.WorkflowTask;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.repository.TestExecutionRepository;

+import org.oran.otf.common.utility.Utility;

+import org.oran.otf.service.impl.DeleteProcessInstanceServiceImpl;

+import com.google.common.base.Strings;

+import com.mongodb.client.result.UpdateResult;

+

+import java.util.Date;

+import java.util.List;

+

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.impl.incident.IncidentContext;

+import org.camunda.bpm.engine.impl.incident.IncidentHandler;

+import org.camunda.bpm.engine.runtime.Execution;

+import org.camunda.bpm.engine.runtime.Incident;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.boot.context.event.ApplicationReadyEvent;

+import org.springframework.context.event.EventListener;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+import org.springframework.stereotype.Service;

+

+@Service

+public class FailedJobIncidentHandler implements IncidentHandler {

+

+  private static final Logger logger = LoggerFactory.getLogger(FailedJobIncidentHandler.class);

+  private static final String logPrefix = Utility.getLoggerPrefix();

+

+  @Autowired

+  private TestExecutionRepository testExecutionRepository;

+  @Autowired

+  private MongoTemplate mongoOperation;

+  @Autowired

+  private DeleteProcessInstanceServiceImpl deleteProcessInstanceService;

+

+  @Override

+  public String getIncidentHandlerType() {

+    return Incident.FAILED_JOB_HANDLER_TYPE;

+  }

+

+  @Override

+  public Incident handleIncident(IncidentContext context, String message) {

+    String executionId = context.getExecutionId();

+    if (Strings.isNullOrEmpty(executionId)) {

+      return null;

+    }

+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();

+

+    Execution execution = runtimeService.createExecutionQuery().executionId(executionId).singleResult();

+    String processInstanceId = execution.getProcessInstanceId();

+    TestExecution testExecution =

+        testExecutionRepository.findFirstByProcessInstanceId(processInstanceId).orElse(null);

+

+    if (testExecution == null) {

+      String error = String.format(

+          "%sUnable to find testExecution with processInstanceId %s. This process instance will forcefully be terminated to avoid a rogue process.",

+          logPrefix, processInstanceId);

+      logger.error(error);

+      deleteProcessInstanceService.deleteProcessInstanceInternal(processInstanceId, error);

+

+    } else {

+      if(!testExecution.getTestResult().equals(TestResult.TERMINATED)){

+        updateTestResult(testExecution, TestResult.WORKFLOW_ERROR, message);

+      }

+      deleteProcessInstanceService.deleteProcessInstanceInternal(processInstanceId, message);

+

+    }

+

+    List<WorkflowTask> workflowTasks =

+        WorkflowTask.workflowTasksByExecutionId.getOrDefault(processInstanceId, null);

+

+    if (workflowTasks != null) {

+      logger.debug("Forcefully terminating workflow tasks for processInstanceId: " + processInstanceId);

+      for (WorkflowTask workflowTask : workflowTasks) {

+        workflowTask.shutdown(true);

+      }

+    }

+

+    return null;

+  }

+

+  private void updateTestResult(TestExecution testExecution, String testResult, String testResultMessage) {

+    // Set the test result

+    testExecution.setTestResult(testResult);

+    testExecution.setTestResultMessage(testResultMessage);

+    Query query = new Query();

+    query.addCriteria(Criteria.where("_id").is(testExecution.get_id()));

+    // Also add businessKey as a criteria because the object won't be found if the business key

+    // was somehow modified in the workflow.

+    query.addCriteria(Criteria.where("businessKey").is(testExecution.getBusinessKey()));

+    Update update = new Update();

+    update.set("testResult", testExecution.getTestResult());

+    update.set("testResultMessage", testExecution.getTestResultMessage());

+    update.set("endTime", new Date(System.currentTimeMillis()));

+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+    // Check the status of the findAndUpdate database, and appropriately handle the errors.

+    if (result.getMatchedCount() == 0) {

+      throw new TestExecutionException(

+          String.format(

+              "Unable to log the test result because a testExecution associated with _id, %s and businessKey %s, was not found.",

+              testExecution.get_id(), testExecution.getBusinessKey()));

+    } else if (result.getModifiedCount() == 0) {

+      throw new TestExecutionException("Unable to persist the testExecution to the database.");

+    }

+  }

+

+  @Override

+  public void resolveIncident(IncidentContext context) {

+    //    logger.info("incident resolved");

+

+  }

+

+  @Override

+  public void deleteIncident(IncidentContext context) {

+    //    logger.info("incident deleted");

+

+  }

+}

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
index 0000000..b90a8e5
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/RsaEncryptDecrypt.java
@@ -0,0 +1,55 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.workflow.utility;

+

+import java.security.KeyPair;

+import java.security.KeyPairGenerator;

+import java.security.NoSuchAlgorithmException;

+import javax.crypto.Cipher;

+import org.springframework.stereotype.Service;

+

+@Service

+public class RsaEncryptDecrypt {

+

+  private KeyPair keyPair;

+

+  public RsaEncryptDecrypt() throws NoSuchAlgorithmException {

+    this.keyPair = buildKeyPair();

+

+  }

+

+  private KeyPair buildKeyPair() throws NoSuchAlgorithmException {

+    final int keySize = 2048;

+    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");

+    keyPairGenerator.initialize(keySize);

+    return keyPairGenerator.genKeyPair();

+  }

+

+  public byte[] encrypt(String message) throws Exception {

+    Cipher cipher = Cipher.getInstance("RSA");

+    cipher.init(Cipher.ENCRYPT_MODE, this.keyPair.getPrivate());

+

+    return cipher.doFinal(message.getBytes());

+  }

+

+  public byte[] decrypt(byte[] encrypted) throws Exception {

+    Cipher cipher = Cipher.getInstance("RSA");

+    cipher.init(Cipher.DECRYPT_MODE, this.keyPair.getPublic());

+

+    return cipher.doFinal(encrypted);

+  }

+}

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
index 0000000..e7302e6
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/WorkflowTask.java
@@ -0,0 +1,169 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.workflow.utility;

+

+import org.oran.otf.common.utility.Utility;

+import com.google.common.base.Strings;

+import com.google.common.util.concurrent.ThreadFactoryBuilder;

+import java.util.ArrayList;

+import java.util.Collections;

+import java.util.List;

+import java.util.Map;

+import java.util.Set;

+import java.util.concurrent.ConcurrentHashMap;

+import java.util.concurrent.ExecutorService;

+import java.util.concurrent.Executors;

+import java.util.concurrent.Future;

+import java.util.concurrent.ThreadFactory;

+import java.util.concurrent.ThreadPoolExecutor;

+import java.util.concurrent.TimeUnit;

+

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

+public class WorkflowTask {

+

+  private static final Logger logger = LoggerFactory.getLogger(WorkflowTask.class);

+  private static final String logPrefix = Utility.getLoggerPrefix();

+

+  public static Map<String, List<WorkflowTask>> workflowTasksByExecutionId =

+      new ConcurrentHashMap<>();

+  // The processInstanceId of the Camunda process instance the thread pool is created under.

+  private final String processInstanceId;

+  // The pool service used to create the fixed thread pool.

+  private final ExecutorService pool;

+  // Used to keep track of all the tasks to be executed, which allows tasks to easily be deleted.

+  private List<Future<?>> futures;

+  // Used to determine if currently running threads should be interrupted

+  private boolean interruptOnFailure;

+

+  public WorkflowTask(String executionId, int threads, boolean interruptOnFailure) {

+    if (threads <= 0 || Strings.isNullOrEmpty(executionId)) {

+      this.processInstanceId = null;

+      this.pool = null;

+      return;

+    }

+

+    ThreadFactory namedThreadFactory =

+        new ThreadFactoryBuilder().setNameFormat(executionId + "-%d").build();

+

+    this.processInstanceId = executionId;

+    this.pool =

+        threads == 1

+            ? Executors.newSingleThreadExecutor()

+            : Executors.newFixedThreadPool(threads, namedThreadFactory);

+    this.futures = Collections.synchronizedList(new ArrayList<>());

+    this.interruptOnFailure = interruptOnFailure;

+

+    synchronized (WorkflowTask.workflowTasksByExecutionId) {

+      if (!WorkflowTask.workflowTasksByExecutionId.containsKey(this.processInstanceId)) {

+        List<WorkflowTask> list = new ArrayList<>();

+        list.add(this);

+        WorkflowTask.workflowTasksByExecutionId.put(

+            this.processInstanceId, Collections.synchronizedList(list));

+      } else {

+        WorkflowTask.workflowTasksByExecutionId.get(this.processInstanceId).add(this);

+      }

+    }

+  }

+

+  public void shutdown() {

+    this.shutdown(this.interruptOnFailure);

+  }

+

+  public void shutdown(boolean interruptOnFailure) {

+    if (interruptOnFailure) {

+      // Cancel currently executing tasks, and halt any waiting tasks.

+      pool.shutdownNow();

+    } else {

+      // Disable new tasks from being submitted, while allowing currently executing tasks to finish.

+      pool.shutdown();

+    }

+

+    try {

+      // Wait a while for existing tasks to terminate

+      if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {

+        for (Future<?> f : futures) {

+          f.cancel(interruptOnFailure);

+        }

+

+        // Wait a while for tasks to respond to being cancelled

+        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {

+          System.err.println("Pool did not terminate");

+        }

+      }

+    } catch (InterruptedException ie) {

+      // (Re-)Cancel if current thread also interrupted

+      pool.shutdownNow();

+      // Preserve interrupt status

+      // Thread.currentThread().interrupt();

+    }

+

+    workflowTasksByExecutionId.remove(this.processInstanceId);

+  }

+

+  public String getProcessInstanceId() {

+    return processInstanceId;

+  }

+

+  public ExecutorService getPool() {

+    return pool;

+  }

+

+  public List<Future<?>> getFutures() {

+    return futures;

+  }

+

+  public void setFutures(List<Future<?>> futures) {

+    this.futures = futures;

+  }

+

+  public static void printWorkflowTaskResources() {

+    for (Map.Entry<String, List<WorkflowTask>> entry : workflowTasksByExecutionId.entrySet()) {

+      logger.info(

+          "{}--------------Parent processInstanceId: {}--------------", logPrefix, entry.getKey());

+

+      List<WorkflowTask> workflowTasks =

+          workflowTasksByExecutionId.getOrDefault(entry.getKey(), null);

+      for (WorkflowTask task : workflowTasks) {

+        task.print();

+      }

+    }

+  }

+

+  public static void printThreadInformation() {

+    Set<Thread> threadSet = Thread.getAllStackTraces().keySet();

+    for (Thread t : threadSet) {

+      if (t.getThreadGroup() == Thread.currentThread().getThreadGroup()) {

+        logger.info("{}{}", logPrefix, t.toString());

+      }

+    }

+  }

+

+  private void print() {

+    logger.info("%sWorkflowTask processInstanceId{})", this.processInstanceId);

+    if (this.pool instanceof ThreadPoolExecutor) {

+      ThreadPoolExecutor tpe = (ThreadPoolExecutor) pool;

+

+      logger.info("\tActive count: {}.", tpe.getActiveCount());

+      logger.info("\tTask status: {}/{}.", tpe.getCompletedTaskCount(), tpe.getTaskCount());

+      logger.info("\tPool size: {}.", tpe.getPoolSize());

+      logger.info("\tCore pool size: {}.", tpe.getCorePoolSize());

+      logger.info("\tQueue size: {}.", tpe.getQueue().size());

+    }

+  }

+}

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
index 0000000..608470e
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/camunda/workflow/utility/WorkflowUtility.java
@@ -0,0 +1,291 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.camunda.workflow.utility;

+

+import static org.camunda.spin.Spin.JSON;

+

+import org.oran.otf.camunda.exception.TestExecutionException;

+import org.oran.otf.camunda.model.ExecutionConstants;

+import org.oran.otf.camunda.model.ExecutionConstants.ExecutionVariable;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.model.local.ParallelFlowInput;

+import org.oran.otf.common.utility.Utility;

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import org.bson.types.ObjectId;

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.camunda.spin.json.SpinJsonNode;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Service;

+

+@Service

+public class WorkflowUtility {

+

+  private static Logger logger = LoggerFactory.getLogger(WorkflowUtility.class);

+  @Autowired

+  private RsaEncryptDecrypt rsaUtility;

+

+  public boolean verifyTestExecutionChecksum(

+      DelegateExecution execution, TestExecution testExecution) {

+    try {

+      byte[] enc = (byte[]) execution.getVariable(ExecutionVariable.TEST_EXECUTION);

+

+      String test = ""; // testExecution.createTestDescription();

+      String dec = new String(rsaUtility.decrypt(enc));

+      if (!dec.equals(test)) {

+        return false;

+        // throw new TestExecutionException("Modification Error: User modified platform data");

+      }

+    } catch (Exception e) {

+      logger.error(

+          execution.getCurrentActivityId()

+              + ": Failed to decrypt test execution. May have been tampered with.\n"

+              + e.getMessage());

+      return false;

+    }

+    return true;

+  }

+

+  public <T> T getExecutionVariable(Map<String, Object> variables, String key, Class<T> type) {

+    Object obj = variables.get(key);

+    if (obj == null) {

+      logger.error(String.format("Failed to get variable because the key %s does not exist.", key));

+    }

+    // return spin json nodes as maps

+    if (obj instanceof SpinJsonNode) {

+      SpinJsonNode node = (SpinJsonNode) obj;

+      if (!node.isObject()) {

+        throw new TestExecutionException(

+            "Unable to retrieve variable as type Map from the execution. Variable was set to SpinJsonNode");

+      }

+      Map<String, Object> map = (Map<String, Object>) node.mapTo(HashMap.class);

+    }

+

+    return type.isInstance(obj) ? type.cast(obj) : null;

+  }

+

+//  public boolean hasPermission(User user, TestInstance testInstance) {

+//    // Groups that the user holds a membership in.

+//    List<UserGroup> userGroups = user.getGroups();

+//    // The groupId associated with the test instance.

+//    ObjectId targetGroupId = testInstance.getGroupId();

+//    // Check if any of the groups has access to the test instance.

+//    UserGroup targetGroup =

+//        userGroups.stream()

+//            .filter(userGroup -> userGroup.getGroupId().equals(targetGroupId))

+//            .findAny()

+//            .orElse(null);

+//

+//    return targetGroup != null;

+//  }

+

+  public TestExecution getTestExecution(Map<String, Object> variables, String logPrefix)

+      throws TestExecutionException {

+    // Get the current test execution object.

+    TestExecution testExecution =

+        this.getExecutionVariable(variables, ExecutionVariable.TEST_EXECUTION, TestExecution.class);

+    // Perform a null-check to ensure it is available. It's critical to throw an exception if it

+    // is not available since the object is essential for results.

+    if (testExecution == null) {

+      logger.error(logPrefix + " Test execution is null.");

+      throw new TestExecutionException("The test execution was not found.");

+    }

+    return testExecution;

+  }

+

+  public Map<String, Object> getTestData(Map<String, Object> variables, String logPrefix)

+      throws TestExecutionException {

+    // Get vthInput from the Camunda execution variable map.

+    @SuppressWarnings({"unchecked"})

+    Map<String, Object> testData =

+        (Map<String, Object>)

+            this.getExecutionVariable(variables, ExecutionVariable.TEST_DATA, Map.class);

+

+    if (testData == null) {

+      throw new TestExecutionException(

+          "Unable to retrieve testData as type Map from the execution.");

+    }

+    return testData;

+  }

+

+  public Object getTestDataByActivity(

+      Map<String, Object> variables, String currentActivityId, String logPrefix)

+      throws TestExecutionException, NullPointerException {

+    // Get vthInput from the Camunda execution variable map.

+    @SuppressWarnings({"unchecked"})

+    Map<String, Object> testData =

+        (Map<String, Object>)

+            this.getExecutionVariable(variables, ExecutionVariable.TEST_DATA, Map.class);

+

+    if (testData == null) {

+      throw new TestExecutionException(

+          "Unable to retrieve testData as type Map from the execution.");

+    }

+    Object activityParameters = testData.get(currentActivityId);

+    if (activityParameters == null) {

+      throw new NullPointerException(

+          logPrefix

+              + String.format(

+              "A testData parameter was not found for the activityId, %s.", currentActivityId));

+    }

+    return activityParameters;

+  }

+

+

+  public Map<String, ParallelFlowInput> getPfloInputByActivity(

+      Map<String, Object> variables, String currentActivityId, String logPrefix)

+      throws TestExecutionException, NullPointerException {

+    // Get vthInput from the Camunda execution variable map.

+    @SuppressWarnings({"unchecked"})

+    Map<String, Object> pfloInput =

+        (Map<String, Object>)

+            this.getExecutionVariable(variables, ExecutionVariable.PFLO_INPUT, Map.class);

+

+    if (pfloInput == null) {

+      throw new TestExecutionException(

+          "Unable to retrieve testData as type Map from the execution.");

+    }

+    Map<String, ParallelFlowInput> activityParameters =

+        (Map<String, ParallelFlowInput>) pfloInput.get(currentActivityId);

+    if (activityParameters == null) {

+      throw new NullPointerException(

+          logPrefix

+              + String.format(

+              "A plfoInput parameter was not found for the activityId, %s.",

+              currentActivityId));

+    }

+    return activityParameters;

+  }

+

+  public List<Map<String, Object>> getVthInput(

+      Map<String, Object> variables, String currentActivityId, String logPrefix)

+      throws TestExecutionException, NullPointerException, IllegalArgumentException {

+    // Get vthInput from the Camunda execution variable map.

+    @SuppressWarnings({"unchecked"})

+    Map<String, Object> vthInput =

+        (Map<String, Object>)

+            this.getExecutionVariable(variables, ExecutionVariable.VTH_INPUT, Map.class);

+

+    if (vthInput == null) {

+      throw new TestExecutionException(

+          "Unable to retrieve vthInput as type Map from the execution.");

+    }

+

+    // Get the current activityId to use as a key to retrieve the vthInput for this task.

+    // vthInput is expected to be a JSON array of size [1, inf)

+    Object oActivityParameters = vthInput.get(currentActivityId);

+    // Throw an exception if no parameters were found for this activity.

+    if (oActivityParameters == null) {

+      throw new NullPointerException(

+          logPrefix

+              + String.format(

+              "A vthInput parameter was not found for the activityId, %s.", currentActivityId));

+    }

+

+    List<Map<String, Object>> lActivityParameters;

+    // Legacy hack

+    try {

+      @SuppressWarnings("unchecked")

+      Map<String, Object> mActivityParameters = new HashMap<>();

+      mActivityParameters.put("method", "post");

+      mActivityParameters.put("payload", Utility.toMap(oActivityParameters));

+      Map<String, Object> headers = new HashMap<>();

+      headers.put("Content-Type", "application/json");

+      mActivityParameters.put("headers", headers);

+      lActivityParameters = new ArrayList();

+      lActivityParameters.add(mActivityParameters);

+    } catch (Exception e) {

+      try {

+        // Try to convert the parameters to an array of "vthInput(s)"

+        lActivityParameters = (List<Map<String, Object>>) Utility.toList(oActivityParameters);

+      } catch (Exception ee) {

+        throw new IllegalArgumentException(

+            String.format("Unable to parse the value for vthInput[%s].", currentActivityId));

+      }

+    }

+    return lActivityParameters;

+  }

+

+  public String getTestResult(Map<String, Object> variables, String logPrefix) {

+    String testResult =

+        this.getExecutionVariable(variables, ExecutionVariable.TEST_RESULT, String.class);

+    // Set the test result to UNKNOWN

+    if (testResult == null) {

+      logger.debug(

+          logPrefix

+              + "Unable to retrieve test result as primitive type String. Setting result to unknown.");

+      testResult = ExecutionConstants.TestResult.UNKNOWN;

+    }

+    return testResult;

+  }

+

+  public String getTestResultMessage(Map<String, Object> variables, String logPrefix) {

+    String testResultMessage =

+            this.getExecutionVariable(variables, ExecutionVariable.TEST_RESULT_MESSAGE, String.class);

+    // Set the test result to UNKNOWN

+    if (testResultMessage == null) {

+      testResultMessage = "";

+//      logger.debug(

+//              logPrefix

+//                      + "Unable to retrieve test result message as primitive type String. Setting message to empty string.");

+//      testResultMessage = "";

+    }

+    return testResultMessage;

+  }

+

+  public Map<String, Object> getTestDetails(Map<String, Object> variables, String logPrefix)

+      throws TestExecutionException {

+    // Get test details as a String because it can be saved as one of many "JSON" types. Then try

+    // to convert it to a generic map.

+    String testDetailsString =

+        this.getExecutionVariable(variables, ExecutionVariable.TEST_DETAILS, String.class);

+    if (testDetailsString != null) {

+      // Use Spin to map the string to a Map.

+      @SuppressWarnings({"unchecked"})

+      Map<String, Object> mTestDetails;

+      try {

+        mTestDetails = JSON(testDetailsString).mapTo(HashMap.class);

+      } catch (Exception e) {

+        logger.error(

+            "Unable to convert testDetails to a map.\nError: "

+                + e.getMessage()

+                + "\ntestDetails: "

+                + testDetailsString);

+        mTestDetails = new HashMap<>();

+      }

+      return mTestDetails;

+    }

+

+    // get testDetails as a map.

+    @SuppressWarnings({"unchecked"})

+    Map<String, Object> testDetails =

+        (Map<String, Object>)

+            this.getExecutionVariable(variables, ExecutionVariable.TEST_DETAILS, Map.class);

+

+    if (testDetails == null) {

+      logger.debug(

+          logPrefix

+              + "Unable to retrieve test details as primitive type String. Setting to an empty JSON.");

+      testDetails = new HashMap<>();

+    }

+    return testDetails;

+  }

+}

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
index 0000000..93162f8
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/Group.java
@@ -0,0 +1,109 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.List;

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "groups")

+public class Group implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  @Id

+  private ObjectId _id;

+  private String groupName;

+  private String groupDescription;

+  private List<ObjectId> mechanizedIds;

+  private ObjectId ownerId;

+  private List<Role> roles;

+  private List<GroupMember> members;

+  private ObjectId parentGroupId;

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getGroupName() {

+    return groupName;

+  }

+

+  public void setGroupName(String groupName) {

+    this.groupName = groupName;

+  }

+

+  public String getGroupDescription() {

+    return groupDescription;

+  }

+

+  public void setGroupDescription(String groupDescription) {

+    this.groupDescription = groupDescription;

+  }

+

+  public List<ObjectId> getMechanizedIds() {

+    return mechanizedIds;

+  }

+

+  public void setMechanizedIds(List<ObjectId> mechanizedIds) {

+    this.mechanizedIds = mechanizedIds;

+  }

+

+  public ObjectId getOwnerId() {

+    return ownerId;

+  }

+

+  public void setOwnerId(ObjectId ownerId) {

+    this.ownerId = ownerId;

+  }

+

+  public List<Role> getRoles() {

+    return roles;

+  }

+

+  public void setRoles(List<Role> roles) {

+    this.roles = roles;

+  }

+

+  public List<GroupMember> getMembers() {

+    return members;

+  }

+

+  public void setMembers(List<GroupMember> members) {

+    this.members = members;

+  }

+

+  public ObjectId getParentGroupId() {

+    return parentGroupId;

+  }

+

+  public void setParentGroupId(ObjectId parentGroupId) {

+    this.parentGroupId = parentGroupId;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..6ab79c2
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/GroupMember.java
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.bson.types.ObjectId;

+

+import java.util.List;

+

+public class GroupMember {

+    private ObjectId userId;

+    private List<String> roles;//this is name of roles assigned to user that are created within the group i.e admin,dev,.. etc

+

+    public ObjectId getUserId() {

+        return userId;

+    }

+

+    public void setUserId(ObjectId userId) {

+        this.userId = userId;

+    }

+

+    public List<String> getRoles() {

+        return roles;

+    }

+

+    public void setRoles(List<String> roles) {

+        this.roles = roles;

+    }

+}

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
index 0000000..aca09f1
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/Role.java
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import java.util.List;

+

+public class Role {

+

+    private String roleName;

+    private List<String> permissions;

+

+    public String getRoleName() {

+        return roleName;

+    }

+

+    public void setRoleName(String roleName) {

+        this.roleName = roleName;

+    }

+

+    public List<String> getPermissions() {

+        return permissions;

+    }

+

+    public void setPermissions(List<String> permissions) {

+        this.permissions = permissions;

+    }

+}

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
index 0000000..b59a746
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/TestDefinition.java
@@ -0,0 +1,138 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.List;

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "testDefinitions")

+public class TestDefinition implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  @Id

+  private ObjectId _id;

+  private String testName;

+  private String testDescription;

+  private String processDefinitionKey;

+  private List<BpmnInstance> bpmnInstances;

+  private ObjectId groupId;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+  private boolean disabled;

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestName() {

+    return testName;

+  }

+

+  public void setTestName(String testName) {

+    this.testName = testName;

+  }

+

+  public String getTestDescription() {

+    return testDescription;

+  }

+

+  public void setTestDescription(String testDescription) {

+    this.testDescription = testDescription;

+  }

+

+  public String getProcessDefinitionKey() {

+    return processDefinitionKey;

+  }

+

+  public void setProcessDefinitionKey(String processDefinitionKey) {

+    this.processDefinitionKey = processDefinitionKey;

+  }

+

+  public List<BpmnInstance> getBpmnInstances() {

+    return bpmnInstances;

+  }

+

+  public void setBpmnInstances(List<BpmnInstance> bpmnInstances) {

+    this.bpmnInstances = bpmnInstances;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  public boolean isDisabled() {

+    return disabled;

+  }

+

+  public void setDisabled(boolean disabled) {

+    this.disabled = disabled;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..85b4a71
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/TestExecution.java
@@ -0,0 +1,235 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.model.historic.TestDefinitionHistoric;

+import org.oran.otf.common.model.historic.TestInstanceHistoric;

+import org.oran.otf.common.model.local.TestHeadResult;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.List;

+import java.util.Map;

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "testExecutions")

+public class TestExecution implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  @Id

+  private ObjectId _id;

+  private ObjectId groupId;

+  private ObjectId executorId;

+

+  private boolean async;

+  private Date startTime;

+  private Date endTime;

+  private String businessKey;

+  private String processInstanceId;

+  private String testResult;

+  private String testResultMessage;

+  private Map<String, Object> testDetails;

+  private List<TestHeadResult> testHeadResults;

+  private List<TestExecution> testInstanceResults;

+  // Stores historic information of associated

+  private String historicEmail;

+  private TestInstanceHistoric historicTestInstance;

+  private TestDefinitionHistoric historicTestDefinition;

+

+  public TestExecution() {

+  }

+

+  public TestExecution(

+      ObjectId _id,

+      ObjectId groupId,

+      ObjectId executorId,

+      boolean async,

+      Date startTime,

+      Date endTime,

+      String businessKey,

+      String processInstanceId,

+      String testResult,

+      String testResultMessage,

+      Map<String, Object> testDetails,

+      List<TestHeadResult> testHeadResults,

+      List<TestExecution> testInstanceResults,

+      String historicEmail,

+      TestInstanceHistoric historicTestInstance,

+      TestDefinitionHistoric historicTestDefinition) {

+    this._id = _id;

+    this.groupId = groupId;

+    this.executorId = executorId;

+    this.async = async;

+    this.startTime = startTime;

+    this.endTime = endTime;

+    this.businessKey = businessKey;

+    this.processInstanceId = processInstanceId;

+    this.testResult = testResult;

+    this.testResultMessage = testResultMessage;

+    this.testDetails = testDetails;

+    this.testHeadResults = testHeadResults;

+    this.testInstanceResults = testInstanceResults;

+    this.historicEmail = historicEmail;

+    this.historicTestInstance = historicTestInstance;

+    this.historicTestDefinition = historicTestDefinition;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public ObjectId getExecutorId() {

+    return executorId;

+  }

+

+  public void setExecutorId(ObjectId executorId) {

+    this.executorId = executorId;

+  }

+

+  public boolean isAsync() {

+    return async;

+  }

+

+  public void setAsync(boolean async) {

+    this.async = async;

+  }

+

+  public Date getStartTime() {

+    return startTime;

+  }

+

+  public void setStartTime(Date startTime) {

+    this.startTime = startTime;

+  }

+

+  public Date getEndTime() {

+    return endTime;

+  }

+

+  public void setEndTime(Date endTime) {

+    this.endTime = endTime;

+  }

+

+  public String getBusinessKey() {

+    return businessKey;

+  }

+

+  public void setBusinessKey(String businessKey) {

+    this.businessKey = businessKey;

+  }

+

+  public String getProcessInstanceId() {

+    return processInstanceId;

+  }

+

+  public void setProcessInstanceId(String processInstanceId) {

+    this.processInstanceId = processInstanceId;

+  }

+

+  public String getTestResult() {

+    return testResult;

+  }

+

+  public void setTestResult(String testResult) {

+    this.testResult = testResult;

+  }

+

+  public String getTestResultMessage() {

+    return testResultMessage;

+  }

+

+  public void setTestResultMessage(String testResultMessage) {

+    this.testResultMessage = testResultMessage;

+  }

+

+  public Map<String, Object> getTestDetails() {

+    return testDetails;

+  }

+

+  public void setTestDetails(Map<String, Object> testDetails) {

+    this.testDetails = testDetails;

+  }

+

+  public List<TestHeadResult> getTestHeadResults() {

+    synchronized (testHeadResults) {

+      return testHeadResults;

+    }

+  }

+

+  public void setTestHeadResults(List<TestHeadResult> testHeadResults) {

+    synchronized (testHeadResults) {

+      this.testHeadResults = testHeadResults;

+    }

+  }

+

+  public List<TestExecution> getTestInstanceResults() {

+    synchronized (testInstanceResults) {

+      return testInstanceResults;

+    }

+  }

+

+  public void setTestInstanceResults(List<TestExecution> testInstanceResults) {

+    synchronized (testInstanceResults) {

+      this.testInstanceResults = testInstanceResults;

+    }

+  }

+

+  public String getHistoricEmail() {

+    return historicEmail;

+  }

+

+  public void setHistoricEmail(String historicEmail) {

+    this.historicEmail = historicEmail;

+  }

+

+  public TestInstanceHistoric getHistoricTestInstance() {

+    return historicTestInstance;

+  }

+

+  public void setHistoricTestInstance(TestInstanceHistoric historicTestInstance) {

+    this.historicTestInstance = historicTestInstance;

+  }

+

+  public TestDefinitionHistoric getHistoricTestDefinition() {

+    return historicTestDefinition;

+  }

+

+  public void setHistoricTestDefinition(

+      TestDefinitionHistoric historicTestDefinition) {

+    this.historicTestDefinition = historicTestDefinition;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..7f4bcbc
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/TestHead.java
@@ -0,0 +1,224 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.Map;

+

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.index.Indexed;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "testHeads")

+public class TestHead implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  @Id

+  private ObjectId _id;

+

+  @Indexed(unique = true)

+  private String testHeadName;

+

+  private String testHeadDescription;

+  private String hostname;

+  private String port;

+  private String resourcePath;

+  private ObjectId creatorId;

+  private ObjectId groupId;

+  private String authorizationType;

+  private String authorizationCredential;

+  private Boolean authorizationEnabled;

+  private Map<String, Object> vthInputTemplate;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId updatedBy;

+  private Boolean isPublic;

+  public TestHead() {

+  }

+

+  public TestHead(

+          ObjectId _id,

+          String testHeadName,

+          String testHeadDescription,

+          String hostname,

+          String port,

+          String resourcePath,

+          ObjectId creatorId,

+          ObjectId groupId,

+          String authorizationType,

+          String authorizationCredential,

+          boolean authorizationEnabled,

+          Map<String, Object> vthInputTemplate,

+          Date createdAt,

+          Date updatedAt,

+          ObjectId updatedBy,

+          Boolean isPublic) {

+    this._id = _id;

+    this.testHeadName = testHeadName;

+    this.testHeadDescription = testHeadDescription;

+    this.hostname = hostname;

+    this.port = port;

+    this.resourcePath = resourcePath;

+    this.creatorId = creatorId;

+    this.groupId = groupId;

+    this.authorizationType = authorizationType;

+    this.authorizationCredential = authorizationCredential;

+    this.authorizationEnabled = authorizationEnabled;

+    this.vthInputTemplate = vthInputTemplate;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.updatedBy = updatedBy;

+    this.isPublic = isPublic;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestHeadName() {

+    return testHeadName;

+  }

+

+  public void setTestHeadName(String testHeadName) {

+    this.testHeadName = testHeadName;

+  }

+

+  public String getTestHeadDescription() {

+    return testHeadDescription;

+  }

+

+  public void setTestHeadDescription(String testHeadDescription) {

+    this.testHeadDescription = testHeadDescription;

+  }

+

+  public String getHostname() {

+    return hostname;

+  }

+

+  public void setHostname(String hostname) {

+    this.hostname = hostname;

+  }

+

+  public String getPort() {

+    return port;

+  }

+

+  public void setPort(String port) {

+    this.port = port;

+  }

+

+  public String getResourcePath() {

+    return resourcePath;

+  }

+

+  public void setResourcePath(String resourcePath) {

+    this.resourcePath = resourcePath;

+  }

+

+  public ObjectId getCreatorId() {

+    return creatorId;

+  }

+

+  public void setCreatorId(ObjectId creatorId) {

+    this.creatorId = creatorId;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public String getAuthorizationCredential() {

+    return authorizationCredential;

+  }

+

+  public String getAuthorizationType() {

+    return authorizationType;

+  }

+

+  public void setAuthorizationType(String authorizationType) {

+    this.authorizationType = authorizationType;

+  }

+

+  public void setAuthorizationCredential(String authorizationCredential) {

+    this.authorizationCredential = authorizationCredential;

+  }

+

+  public Boolean getAuthorizationEnabled() {

+    return authorizationEnabled;

+  }

+

+  public void setAuthorizationEnabled(Boolean authorizationEnabled) {

+    this.authorizationEnabled = authorizationEnabled;

+  }

+

+  public Map<String, Object> getVthInputTemplate() {

+    return vthInputTemplate;

+  }

+

+  public void setVthInputTemplate(Map<String, Object> vthInputTemplate) {

+    this.vthInputTemplate = vthInputTemplate;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  public Boolean isPublic() {

+    return isPublic;

+  }

+

+  public void setPublic(Boolean aPublic) {

+    isPublic = aPublic;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..96fcfa9
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/TestInstance.java
@@ -0,0 +1,259 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.model.local.ParallelFlowInput;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.HashMap;

+import java.util.Map;

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "testInstances")

+public class TestInstance implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private @Id

+  ObjectId _id;

+  private String testInstanceName;

+  private String testInstanceDescription;

+  private ObjectId groupId;

+  private ObjectId testDefinitionId;

+  private String processDefinitionId;

+  private boolean useLatestTestDefinition;

+  private boolean disabled;

+  private boolean simulationMode;

+  private long maxExecutionTimeInMillis;

+  private Map<String, ParallelFlowInput> pfloInput;

+  private Map<String, Object> internalTestData;

+  private Map<String, Object> simulationVthInput;

+  private Map<String, Object> testData;

+  private Map<String, Object> vthInput;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+

+  public TestInstance() {

+  }

+

+  public TestInstance(

+      ObjectId _id,

+      String testInstanceName,

+      String testInstanceDescription,

+      ObjectId groupId,

+      ObjectId testDefinitionId,

+      String processDefinitionId,

+      boolean useLatestTestDefinition,

+      boolean disabled,

+      boolean simulationMode,

+      long maxExecutionTimeInMillis,

+      HashMap<String, ParallelFlowInput> pfloInput,

+      HashMap<String, Object> internalTestData,

+      HashMap<String, Object> simulationVthInput,

+      HashMap<String, Object> testData,

+      HashMap<String, Object> vthInput,

+      Date createdAt,

+      Date updatedAt,

+      ObjectId createdBy,

+      ObjectId updatedBy) {

+    this._id = _id;

+    this.testInstanceName = testInstanceName;

+    this.testInstanceDescription = testInstanceDescription;

+    this.groupId = groupId;

+    this.testDefinitionId = testDefinitionId;

+    this.processDefinitionId = processDefinitionId;

+    this.useLatestTestDefinition = useLatestTestDefinition;

+    this.disabled = disabled;

+    this.simulationMode = simulationMode;

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+    this.pfloInput = pfloInput;

+    this.internalTestData = internalTestData;

+    this.simulationVthInput = simulationVthInput;

+    this.testData = testData;

+    this.vthInput = vthInput;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.createdBy = createdBy;

+    this.updatedBy = updatedBy;

+  }

+

+  public static long getSerialVersionUID() {

+    return serialVersionUID;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestInstanceName() {

+    return testInstanceName;

+  }

+

+  public void setTestInstanceName(String testInstanceName) {

+    this.testInstanceName = testInstanceName;

+  }

+

+  public String getTestInstanceDescription() {

+    return testInstanceDescription;

+  }

+

+  public void setTestInstanceDescription(String testInstanceDescription) {

+    this.testInstanceDescription = testInstanceDescription;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public ObjectId getTestDefinitionId() {

+    return testDefinitionId;

+  }

+

+  public void setTestDefinitionId(ObjectId testDefinitionId) {

+    this.testDefinitionId = testDefinitionId;

+  }

+

+  public String getProcessDefinitionId() {

+    return processDefinitionId;

+  }

+

+  public void setProcessDefinitionId(String processDefinitionId) {

+    this.processDefinitionId = processDefinitionId;

+  }

+

+  public boolean isUseLatestTestDefinition() {

+    return useLatestTestDefinition;

+  }

+

+  public void setUseLatestTestDefinition(boolean useLatestTestDefinition) {

+    this.useLatestTestDefinition = useLatestTestDefinition;

+  }

+

+  public boolean isDisabled() {

+    return disabled;

+  }

+

+  public void setDisabled(boolean disabled) {

+    this.disabled = disabled;

+  }

+

+  public boolean isSimulationMode() {

+    return simulationMode;

+  }

+

+  public void setSimulationMode(boolean simulationMode) {

+    this.simulationMode = simulationMode;

+  }

+

+  public long getMaxExecutionTimeInMillis() {

+    return maxExecutionTimeInMillis;

+  }

+

+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+  }

+

+  public Map<String, ParallelFlowInput> getPfloInput() {

+    return pfloInput;

+  }

+

+  public void setPfloInput(HashMap<String, ParallelFlowInput> pfloInput) {

+    this.pfloInput = pfloInput;

+  }

+

+  public Map<String, Object> getInternalTestData() {

+    return internalTestData;

+  }

+

+  public void setInternalTestData(HashMap<String, Object> internalTestData) {

+    this.internalTestData = internalTestData;

+  }

+

+  public Map<String, Object> getSimulationVthInput() {

+    return simulationVthInput;

+  }

+

+  public void setSimulationVthInput(HashMap<String, Object> simulationVthInput) {

+    this.simulationVthInput = simulationVthInput;

+  }

+

+  public Map<String, Object> getTestData() {

+    return testData;

+  }

+

+  public void setTestData(HashMap<String, Object> testData) {

+    this.testData = testData;

+  }

+

+  public Map<String, Object> getVthInput() {

+    return vthInput;

+  }

+

+  public void setVthInput(HashMap<String, Object> vthInput) {

+    this.vthInput = vthInput;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..9262b52
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/User.java
@@ -0,0 +1,139 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.model.local.UserGroup;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.List;

+import org.bson.types.ObjectId;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "users")

+public class User implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId _id;

+  private List<String> permissions;

+  private String firstName;

+  private String lastName;

+  private String email;

+  private String password;

+  private List<UserGroup> groups;

+  private Date createdAt;

+  private Date updatedAt;

+

+  public User(

+      ObjectId _id,

+      List<String> permissions,

+      String firstName,

+      String lastName,

+      String email,

+      String password,

+      List<UserGroup> groups,

+      Date createdAt,

+      Date updatedAt) {

+    this._id = _id;

+    this.permissions = permissions;

+    this.firstName = firstName;

+    this.lastName = lastName;

+    this.email = email;

+    this.password = password;

+    this.groups = groups;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public List<String> getPermissions() {

+    return permissions;

+  }

+

+  public void setPermissions(List<String> permissions) {

+    this.permissions = permissions;

+  }

+

+  public String getFirstName() {

+    return firstName;

+  }

+

+  public void setFirstName(String firstName) {

+    this.firstName = firstName;

+  }

+

+  public String getLastName() {

+    return lastName;

+  }

+

+  public void setLastName(String lastName) {

+    this.lastName = lastName;

+  }

+

+  public String getEmail() {

+    return email;

+  }

+

+  public void setEmail(String email) {

+    this.email = email;

+  }

+

+  public String getPassword() {

+    return password;

+  }

+

+  public void setPassword(String password) {

+    this.password = password;

+  }

+

+  public List<UserGroup> getGroups() {

+    return groups;

+  }

+

+  public void setGroups(List<UserGroup> groups) {

+    this.groups = groups;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..aa32b5b
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/historic/TestDefinitionHistoric.java
@@ -0,0 +1,188 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.historic;

+

+import org.oran.otf.common.model.TestDefinition;

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.ArrayList;

+import java.util.Date;

+import java.util.List;

+import org.bson.types.ObjectId;

+

+public class TestDefinitionHistoric implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId _id;

+  private String testName;

+  private String testDescription;

+  private String processDefinitionKey;

+  private List<BpmnInstance> bpmnInstances;

+  private ObjectId groupId;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+

+  public TestDefinitionHistoric() {

+  }

+

+  public TestDefinitionHistoric(TestDefinition testDefinition, String processDefinitionId) {

+    this._id = testDefinition.get_id();

+    this.testName = testDefinition.getTestName();

+    this.testDescription = testDefinition.getTestDescription();

+    this.processDefinitionKey = testDefinition.getProcessDefinitionKey();

+    this.bpmnInstances =

+        getHistoricBpmnInstanceAsList(testDefinition.getBpmnInstances(), processDefinitionId);

+    this.groupId = testDefinition.getGroupId();

+    this.createdAt = testDefinition.getCreatedAt();

+    this.updatedAt = testDefinition.getUpdatedAt();

+    this.createdBy = testDefinition.getCreatedBy();

+    this.updatedBy = testDefinition.getUpdatedBy();

+  }

+

+  public TestDefinitionHistoric(

+      ObjectId _id,

+      String testName,

+      String testDescription,

+      String processDefinitionKey,

+      List<BpmnInstance> bpmnInstances,

+      ObjectId groupId,

+      Date createdAt,

+      Date updatedAt,

+      ObjectId createdBy,

+      ObjectId updatedBy) {

+    this._id = _id;

+    this.testName = testName;

+    this.testDescription = testDescription;

+    this.processDefinitionKey = processDefinitionKey;

+    this.bpmnInstances = bpmnInstances;

+    this.groupId = groupId;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.createdBy = createdBy;

+    this.updatedBy = updatedBy;

+  }

+

+  private List<BpmnInstance> getHistoricBpmnInstanceAsList(

+      List<BpmnInstance> bpmnInstances, String processDefinitionId) {

+    BpmnInstance bpmnInstance =

+        bpmnInstances.stream()

+            .filter(

+                _bpmnInstance -> {

+                  return _bpmnInstance.isDeployed()

+                      && _bpmnInstance.getProcessDefinitionId() != null

+                      && _bpmnInstance.getProcessDefinitionId().equals(processDefinitionId);

+                })

+            .findFirst()

+            .orElse(null);

+

+    List<BpmnInstance> historicBpmnInstance = new ArrayList<>();

+    if (bpmnInstance != null) {

+      historicBpmnInstance.add(bpmnInstance);

+    }

+

+    return historicBpmnInstance;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestName() {

+    return testName;

+  }

+

+  public void setTestName(String testName) {

+    this.testName = testName;

+  }

+

+  public String getTestDescription() {

+    return testDescription;

+  }

+

+  public void setTestDescription(String testDescription) {

+    this.testDescription = testDescription;

+  }

+

+  public String getProcessDefinitionKey() {

+    return processDefinitionKey;

+  }

+

+  public void setProcessDefinitionKey(String processDefinitionKey) {

+    this.processDefinitionKey = processDefinitionKey;

+  }

+

+  public List<BpmnInstance> getBpmnInstances() {

+    return bpmnInstances;

+  }

+

+  public void setBpmnInstances(List<BpmnInstance> bpmnInstances) {

+    this.bpmnInstances = bpmnInstances;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..1263893
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/historic/TestInstanceHistoric.java
@@ -0,0 +1,234 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.historic;

+

+import org.oran.otf.common.model.TestInstance;

+import org.oran.otf.common.model.local.ParallelFlowInput;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.HashMap;

+import java.util.Map;

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+

+public class TestInstanceHistoric implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private @Id

+  ObjectId _id;

+  private String testInstanceName;

+  private String testInstanceDescription;

+  private ObjectId groupId;

+  private ObjectId testDefinitionId;

+  private String processDefinitionId;

+  private Map<String, ParallelFlowInput> pfloInput;

+  private Map<String, Object> simulationVthInput;

+  private Map<String, Object> testData;

+  private Map<String, Object> vthInput;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+  private boolean simulationMode;

+

+  public TestInstanceHistoric() {

+  }

+

+  public TestInstanceHistoric(TestInstance testInstance) {

+    this._id = testInstance.get_id();

+    this.testInstanceName = testInstance.getTestInstanceName();

+    this.testInstanceDescription = testInstance.getTestInstanceDescription();

+    this.groupId = testInstance.getGroupId();

+    this.testDefinitionId = testInstance.getTestDefinitionId();

+    this.pfloInput = testInstance.getPfloInput();

+    this.processDefinitionId = testInstance.getProcessDefinitionId();

+    this.simulationVthInput = testInstance.getSimulationVthInput();

+    this.testData = testInstance.getTestData();

+    this.vthInput = testInstance.getVthInput();

+    this.createdAt = testInstance.getCreatedAt();

+    this.updatedAt = testInstance.getUpdatedAt();

+    this.createdBy = testInstance.getCreatedBy();

+    this.updatedBy = testInstance.getUpdatedBy();

+    this.simulationMode = testInstance.isSimulationMode();

+  }

+

+  public TestInstanceHistoric(

+      ObjectId _id,

+      String testInstanceName,

+      String testInstanceDescription,

+      ObjectId groupId,

+      ObjectId testDefinitionId,

+      String processDefinitionId,

+      HashMap<String, ParallelFlowInput> pfloInput,

+      HashMap<String, Object> simulationVthInput,

+      HashMap<String, Object> testData,

+      HashMap<String, Object> vthInput,

+      Date createdAt,

+      Date updatedAt,

+      ObjectId createdBy,

+      ObjectId updatedBy,

+      boolean simulationMode) {

+    this._id = _id;

+    this.testInstanceName = testInstanceName;

+    this.testInstanceDescription = testInstanceDescription;

+    this.groupId = groupId;

+    this.testDefinitionId = testDefinitionId;

+    this.processDefinitionId = processDefinitionId;

+    this.pfloInput = pfloInput;

+    this.simulationVthInput = simulationVthInput;

+    this.testData = testData;

+    this.vthInput = vthInput;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.createdBy = createdBy;

+    this.updatedBy = updatedBy;

+    this.simulationMode = simulationMode;

+  }

+

+  public static long getSerialVersionUID() {

+    return serialVersionUID;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestInstanceName() {

+    return testInstanceName;

+  }

+

+  public void setTestInstanceName(String testInstanceName) {

+    this.testInstanceName = testInstanceName;

+  }

+

+  public String getTestInstanceDescription() {

+    return testInstanceDescription;

+  }

+

+  public void setTestInstanceDescription(String testInstanceDescription) {

+    this.testInstanceDescription = testInstanceDescription;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public ObjectId getTestDefinitionId() {

+    return testDefinitionId;

+  }

+

+  public void setTestDefinitionId(ObjectId testDefinitionId) {

+    this.testDefinitionId = testDefinitionId;

+  }

+

+  public String getProcessDefinitionId() {

+    return processDefinitionId;

+  }

+

+  public void setProcessDefinitionId(String processDefinitionId) {

+    this.processDefinitionId = processDefinitionId;

+  }

+

+  public Map<String, ParallelFlowInput> getPfloInput() {

+    return pfloInput;

+  }

+

+  public void setPfloInput(

+      HashMap<String, ParallelFlowInput> pfloInput) {

+    this.pfloInput = pfloInput;

+  }

+

+  public Map<String, Object> getSimulationVthInput() {

+    return simulationVthInput;

+  }

+

+  public void setSimulationVthInput(

+      HashMap<String, Object> simulationVthInput) {

+    this.simulationVthInput = simulationVthInput;

+  }

+

+  public Map<String, Object> getTestData() {

+    return testData;

+  }

+

+  public void setTestData(HashMap<String, Object> testData) {

+    this.testData = testData;

+  }

+

+  public Map<String, Object> getVthInput() {

+    return vthInput;

+  }

+

+  public void setVthInput(HashMap<String, Object> vthInput) {

+    this.vthInput = vthInput;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  public boolean isSimulationMode() {

+    return simulationMode;

+  }

+

+  public void setSimulationMode(boolean simulationMode) {

+    this.simulationMode = simulationMode;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..580e86c
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/BpmnInstance.java
@@ -0,0 +1,187 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.List;

+import java.util.Map;

+import org.bson.types.ObjectId;

+

+public class BpmnInstance implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private String processDefinitionId;

+  private String deploymentId;

+  private int version;

+  private ObjectId bpmnFileId;

+  private ObjectId resourceFileId;

+  private boolean isDeployed;

+  private List<TestHeadNode> testHeads;

+  private List<PfloNode> pflos;

+  private Map<String, Object> testDataTemplate;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+

+  public BpmnInstance() {

+  }

+

+  public BpmnInstance(

+      String processDefinitionId,

+      String deploymentId,

+      int version,

+      ObjectId bpmnFileId,

+      ObjectId resourceFileId,

+      boolean isDeployed,

+      List<TestHeadNode> testHeads,

+      List<PfloNode> pflos,

+      Map<String, Object> testDataTemplate,

+      Date createdAt,

+      Date updatedAt,

+      ObjectId createdBy,

+      ObjectId updatedBy) {

+    this.processDefinitionId = processDefinitionId;

+    this.deploymentId = deploymentId;

+    this.version = version;

+    this.bpmnFileId = bpmnFileId;

+    this.resourceFileId = resourceFileId;

+    this.isDeployed = isDeployed;

+    this.testHeads = testHeads;

+    this.testDataTemplate = testDataTemplate;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.createdBy = createdBy;

+    this.updatedBy = updatedBy;

+  }

+

+  public String getProcessDefinitionId() {

+    return processDefinitionId;

+  }

+

+  public void setProcessDefinitionId(String processDefinitionId) {

+    this.processDefinitionId = processDefinitionId;

+  }

+

+  public String getDeploymentId() {

+    return deploymentId;

+  }

+

+  public void setDeploymentId(String deploymentId) {

+    this.deploymentId = deploymentId;

+  }

+

+  public int getVersion() {

+    return version;

+  }

+

+  public void setVersion(int version) {

+    this.version = version;

+  }

+

+  public ObjectId getBpmnFileId() {

+    return bpmnFileId;

+  }

+

+  public void setBpmnFileId(ObjectId bpmnFileId) {

+    this.bpmnFileId = bpmnFileId;

+  }

+

+  public ObjectId getResourceFileId() {

+    return resourceFileId;

+  }

+

+  public void setResourceFileId(ObjectId resourceFileId) {

+    this.resourceFileId = resourceFileId;

+  }

+

+  public boolean isDeployed() {

+    return isDeployed;

+  }

+

+  public void setDeployed(boolean deployed) {

+    isDeployed = deployed;

+  }

+

+  public List<TestHeadNode> getTestHeads() {

+    return testHeads;

+  }

+

+  public void setTestHeads(List<TestHeadNode> testHeads) {

+    this.testHeads = testHeads;

+  }

+

+  public List<PfloNode> getPflos() {

+    return pflos;

+  }

+

+  public void setPflos(List<PfloNode> pflos) {

+    this.pflos = pflos;

+  }

+

+  public Map<String, Object> getTestDataTemplate() {

+    return testDataTemplate;

+  }

+

+  public void setTestDataTemplate(Map<String, Object> testDataTemplate) {

+    this.testDataTemplate = testDataTemplate;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  private String getObjectIdString(ObjectId value) {

+    return value == null ? "\"\"" : "\"" + value.toString() + "\"";

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..e294d44
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/DMaaPRequest.java
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import com.fasterxml.jackson.annotation.JsonProperty;

+

+public class DMaaPRequest {

+    private String hostname;

+    private String asyncTopic;

+    private boolean requiresProxy;

+

+    public DMaaPRequest() {

+    }

+

+    public DMaaPRequest(

+            @JsonProperty(value = "hostname", required = true) String hostname,

+            @JsonProperty(value = "asyncTopic", required = true) String asyncTopic,

+            @JsonProperty(value = "requriesProxy", required = false) boolean requiresProxy) {

+        this.hostname = hostname;

+        this.asyncTopic = asyncTopic;

+        this.requiresProxy = requiresProxy;

+

+    }

+

+    public String getHostname() {

+        return hostname;

+    }

+

+    public void setHostname(String hostname) {

+        this.hostname = hostname;

+    }

+

+    public String getAsyncTopic() {

+        return asyncTopic;

+    }

+

+    public void setAsyncTopic(String asyncTopic) {

+        this.asyncTopic = asyncTopic;

+    }

+

+    public boolean getRequiresProxy(){

+        return this.requiresProxy;

+    }

+

+    public void setRequiresProxy(boolean requiresProxy) {

+        this.requiresProxy = requiresProxy;

+    }

+

+    @Override

+    public String toString() {

+        return Convert.objectToJson(this);

+    }

+}

+

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
index 0000000..cd8ed5a
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFApiResponse.java
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+

+import java.util.Date;

+

+public class OTFApiResponse {

+

+  private int statusCode;

+  private String message;

+  private Date time;

+

+  public OTFApiResponse() {

+  }

+

+  public OTFApiResponse(int statusCode, String message) {

+    this.statusCode = statusCode;

+    this.message = message;

+    this.time = new Date(System.currentTimeMillis());

+  }

+

+  public int getStatusCode() {

+    return statusCode;

+  }

+

+  public void setStatusCode(int statusCode) {

+    this.statusCode = statusCode;

+  }

+

+  public String getMessage() {

+    return message;

+  }

+

+  public void setMessage(String message) {

+    this.message = message;

+  }

+

+  public Date getTime() {

+    return time;

+  }

+

+  public void setTime(Date time) {

+    this.time = time;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..59c3dcd
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFDeploymentResponse.java
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+public class OTFDeploymentResponse {

+

+    private String deploymentId;

+    private String processDefinitionKey;

+    private String processDefinitionId;

+    private int version;

+

+    public OTFDeploymentResponse() {

+    }

+

+    public OTFDeploymentResponse(String deploymentId, String processDefinitionKey, String processDefinitionId, int version) {

+        this.deploymentId = deploymentId;

+        this.processDefinitionKey = processDefinitionKey;

+        this.processDefinitionId = processDefinitionId;

+        this.version = version;

+    }

+

+    public String getDeploymentId() {

+        return deploymentId;

+    }

+

+    public void setDeploymentId(String deploymentId) {

+        this.deploymentId = deploymentId;

+    }

+

+    public String getProcessDefinitionKey() {

+        return processDefinitionKey;

+    }

+

+    public void setProcessDefinitionKey(String processDefinitionKey) {

+        this.processDefinitionKey = processDefinitionKey;

+    }

+

+    public String getProcessDefinitionId() {

+        return processDefinitionId;

+    }

+

+    public void setProcessDefinitionId(String processDefinitionId) {

+        this.processDefinitionId = processDefinitionId;

+    }

+

+    public int getVersion() {

+        return version;

+    }

+

+    public void setVersion(int version) {

+        this.version = version;

+    }

+}

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
index 0000000..dd1ecf6
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/OTFProcessInstanceCompletionResponse.java
@@ -0,0 +1,166 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.camunda.model.ExecutionConstants;

+import org.camunda.bpm.engine.history.*;

+import org.camunda.bpm.engine.impl.history.event.HistoricExternalTaskLogEntity;

+import org.camunda.bpm.engine.impl.persistence.entity.HistoricJobLogEventEntity;

+import org.camunda.bpm.engine.impl.persistence.entity.HistoricVariableInstanceEntity;

+

+import java.io.Serializable;

+import java.util.ArrayList;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+

+public class OTFProcessInstanceCompletionResponse implements Serializable {

+    private HistoricProcessInstance historicProcessInstance;

+    private List<HistoricActivityInstance> historicActivityInstance;

+    private List<HistoricIncident> historicIncident;

+    private List<Map<String, Object>> historicJobLog;

+    private List<Map<String, Object>> historicExternalTaskLog;

+    private List<Map<String, Object>> historicVariableInstance;

+

+    public OTFProcessInstanceCompletionResponse() {

+    }

+

+

+    public HistoricProcessInstance getHistoricProcessInstance() {

+        return historicProcessInstance;

+    }

+

+    public void setHistoricProcessInstance(HistoricProcessInstance historicProcessInstance) {

+        this.historicProcessInstance = historicProcessInstance;

+    }

+

+    public List<HistoricActivityInstance> getHistoricActivityInstance() {

+        return historicActivityInstance;

+    }

+

+    public void setHistoricActivityInstance(List<HistoricActivityInstance> historicActivityInstance) {

+        this.historicActivityInstance = historicActivityInstance;

+    }

+

+    public List<HistoricIncident> getHistoricIncident() {

+        return historicIncident;

+    }

+

+    public void setHistoricIncident(List<HistoricIncident> historicIncident) {

+        this.historicIncident = historicIncident;

+    }

+

+    public List<Map<String, Object>> getHistoricJobLog() {

+        return historicJobLog;

+    }

+

+    public void setHistoricJobLog(List<HistoricJobLog> historicJobLog) {

+        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

+        for(HistoricJobLog jobLog: historicJobLog){

+            HistoricJobLogEventEntity log = (HistoricJobLogEventEntity) jobLog;

+            HashMap map = new HashMap();

+

+            map.put("id", log.getId());

+            map.put("executionId", log.getExecutionId());

+            map.put("activityId", log.getActivityId());

+            map.put("eventType", log.getEventType());

+            map.put("sequenceCounter", log.getSequenceCounter());

+            map.put("retries", log.getJobRetries());

+            map.put("jobExceptionMessage", log.getJobExceptionMessage());

+            map.put("jobDefinitionType", log.getJobDefinitionType());

+            map.put("jobDefinitionConfiguration", log.getJobDefinitionConfiguration());

+            map.put("processDefinitionKey", log.getProcessDefinitionKey());

+            map.put("state", convertState(log.getState()));

+

+            list.add(map);

+        }

+        this.historicJobLog = list;

+    }

+

+    public List<Map<String, Object>> getHistoricExternalTaskLog() {

+        return this.historicExternalTaskLog;

+    }

+

+    public void setHistoricExternalTaskLog(List<HistoricExternalTaskLog> historicExternalTaskLog) {

+        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

+        for(HistoricExternalTaskLog externalTaskLog: historicExternalTaskLog){

+            HistoricExternalTaskLogEntity log = (HistoricExternalTaskLogEntity) externalTaskLog;

+            HashMap map = new HashMap();

+

+            map.put("id", log.getId());

+            map.put("executionId", log.getExecutionId());

+            map.put("activityId", log.getActivityId());

+            map.put("state", convertState(log.getState()));

+            map.put("retries", log.getRetries());

+            map.put("processDefinitionKey", log.getProcessDefinitionKey());

+            map.put("errorMessage", log.getErrorMessage());

+            try {

+                map.put("errorDetails", log.getErrorDetails());

+            }

+            catch (Exception e){}

+            map.put("workerId", log.getWorkerId());

+            map.put("topic", log.getTopicName());

+            list.add(map);

+        }

+        this.historicExternalTaskLog = list;

+    }

+

+    public List<Map<String, Object>> getHistoricVariableInstance() {

+        return historicVariableInstance;

+    }

+

+    public void setHistoricVariableInstance(List<HistoricVariableInstance> historicVariableInstance) {

+        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();

+        for(HistoricVariableInstance variableInstanceEntity: historicVariableInstance){

+            HistoricVariableInstanceEntity variable = (HistoricVariableInstanceEntity) variableInstanceEntity;

+            HashMap map = new HashMap();

+            if (variable.getVariableName().equalsIgnoreCase(ExecutionConstants.ExecutionVariable.TEST_EXECUTION)){

+                continue;

+            }

+            map.put("id", variable.getId());

+            map.put("executionId", variable.getExecutionId());

+            map.put("processDefinitionKey", variable.getProcessDefinitionKey());

+            map.put("taskId", variable.getTaskId());

+            map.put("eventType", variable.getVariableName());

+            map.put("errorMessage", variable.getErrorMessage());

+            map.put("state", variable.getState());

+            map.put("variableName", variable.getVariableName());

+            map.put("type",  variable.getTypedValue().getType());

+            map.put("value",  variable.getTypedValue().getValue());

+            map.put("persistentState", variable.getPersistentState());

+

+            list.add(map);

+        }

+        this.historicVariableInstance = list;

+    }

+

+    private String convertState(int state){

+        switch (state){

+            case 0 :

+                return "CREATED";

+            case 1 :

+                return "FAILED";

+            case 2 :

+                return "SUCCESSFUL";

+            case 3 :

+                return "DELETED";

+            default:

+                return "UNKNOWN";

+        }

+    }

+

+}

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
index 0000000..62ee1c8
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/ParallelFlowInput.java
@@ -0,0 +1,91 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.camunda.workflow.WorkflowRequest;

+import org.oran.otf.common.utility.gson.Convert;

+import com.fasterxml.jackson.annotation.JsonCreator;

+import com.fasterxml.jackson.annotation.JsonProperty;

+

+import java.io.Serializable;

+import java.util.List;

+

+public class ParallelFlowInput implements Serializable {

+

+    private static final long serialVersionUID = 1L;

+

+    private List<WorkflowRequest> args;

+    private boolean interruptOnFailure;

+    private int maxFailures;

+    private int threadPoolSize;

+

+    public ParallelFlowInput() {

+    }

+

+    @JsonCreator

+    public ParallelFlowInput(

+        @JsonProperty(value = "args", required = true) List<WorkflowRequest> args,

+        @JsonProperty(value = "interruptOnFailure", required = true) boolean interruptOnFailure,

+        @JsonProperty(value = "maxFailures", required = true) int maxFailures,

+        @JsonProperty(value = "threadPoolSize", required = true) int threadPoolSize) {

+        this.args = args;

+        this.interruptOnFailure = interruptOnFailure;

+        this.maxFailures = maxFailures;

+        this.threadPoolSize = threadPoolSize;

+    }

+

+    public static long getSerialVersionUID() {

+        return serialVersionUID;

+    }

+

+    public List<WorkflowRequest> getArgs() {

+        return args;

+    }

+

+    public void setArgs(List<WorkflowRequest> args) {

+        this.args = args;

+    }

+

+    public boolean isInterruptOnFailure() {

+        return interruptOnFailure;

+    }

+

+    public void setInterruptOnFailure(boolean interruptOnFailure) {

+        this.interruptOnFailure = interruptOnFailure;

+    }

+

+    public int getMaxFailures() {

+        return maxFailures;

+    }

+

+    public void setMaxFailures(int maxFailures) {

+        this.maxFailures = maxFailures;

+    }

+

+    public int getThreadPoolSize() {

+        return threadPoolSize;

+    }

+

+    public void setThreadPoolSize(int threadPoolSize) {

+        this.threadPoolSize = threadPoolSize;

+    }

+

+    @Override

+    public String toString() {

+        return Convert.objectToJson(this);

+    }

+}

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
index 0000000..f70b8a6
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/PfloNode.java
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+

+import java.io.Serializable;

+

+public class PfloNode implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private String bpmnPlfoTaskId;

+  private String label;

+

+  public PfloNode() {

+  }

+

+  public PfloNode(String bpmnPlfoTaskId, String label) {

+    this.bpmnPlfoTaskId = bpmnPlfoTaskId;

+    this.label = label;

+  }

+

+  public static long getSerialVersionUID() {

+    return serialVersionUID;

+  }

+

+  public String getBpmnPlfoTaskId() {

+    return bpmnPlfoTaskId;

+  }

+

+  public void setBpmnPlfoTaskId(String bpmnPlfoTaskId) {

+    this.bpmnPlfoTaskId = bpmnPlfoTaskId;

+  }

+

+  public String getLabel() {

+    return label;

+  }

+

+  public void setLabel(String label) {

+    this.label = label;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..99ed995
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadNode.java
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import org.bson.types.ObjectId;

+

+public class TestHeadNode implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId testHeadId;

+  private String bpmnVthTaskId;

+

+  public TestHeadNode() {

+  }

+

+  public TestHeadNode(ObjectId testHeadId, String taskId) {

+    this.testHeadId = testHeadId;

+    this.bpmnVthTaskId = taskId;

+  }

+

+  public ObjectId getTestHeadId() {

+    return testHeadId;

+  }

+

+  public void setTestHeadId(ObjectId testHeadId) {

+    this.testHeadId = testHeadId;

+  }

+

+  public String getBpmnVthTaskId() {

+    return bpmnVthTaskId;

+  }

+

+  public void setBpmnVthTaskId(String bpmnVthTaskId) {

+    this.bpmnVthTaskId = bpmnVthTaskId;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..5bebd80
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadRequest.java
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import java.io.Serializable;

+import java.util.Map;

+

+public class TestHeadRequest implements Serializable {

+  private static final long serialVersionUID = 1L;

+  private Map<String, String> headers;

+  private Map<String, Object>  body;

+

+  public TestHeadRequest(){}

+

+  public TestHeadRequest(Map<String, String> headers,

+      Map<String, Object> body) {

+    this.headers = headers;

+    this.body = body;

+  }

+

+  public Map<String, String> getHeaders() {

+    return headers;

+  }

+

+  public void setHeaders(Map<String, String> headers) {

+    this.headers = headers;

+  }

+

+  public Map<String, Object> getBody() {

+    return body;

+  }

+

+  public void setBody(Map<String, Object> body) {

+    this.body = body;

+  }

+

+

+

+}

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
index 0000000..33746c1
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/TestHeadResult.java
@@ -0,0 +1,145 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.Map;

+import org.bson.types.ObjectId;

+

+public class TestHeadResult implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId testHeadId;

+  private String testHeadName;

+  private ObjectId testHeadGroupId;

+  private String bpmnVthTaskId;

+

+  //TODO: RG Remove maps below, setters and getters to return to normal

+  //private Map<String, String> testHeadHeaders;

+  //private int testHeadCode;

+  private int statusCode;

+

+  private TestHeadRequest testHeadRequest;

+  private Map<String, Object> testHeadResponse;

+  private Date startTime;

+  private Date endTime;

+

+  public TestHeadResult() {

+  }

+

+  public TestHeadResult(

+      ObjectId testHeadId,

+      String testHeadName,

+      ObjectId testHeadGroupId,

+      String bpmnVthTaskId,

+

+      //TODO: RG changed code to int and changed testHeadRequest from Map<String, String> to RequestContent

+      int statusCode,

+

+      TestHeadRequest testHeadRequest,

+      Map<String, Object> testHeadResponse,

+      Date startTime,

+      Date endTime) {

+    this.testHeadId = testHeadId;

+    this.testHeadName = testHeadName;

+    this.testHeadGroupId = testHeadGroupId;

+    this.bpmnVthTaskId = bpmnVthTaskId;

+

+    //this.testHeadHeaders = testHeadHeaders;

+    this.statusCode = statusCode;

+

+    this.testHeadRequest = testHeadRequest;

+    this.testHeadResponse = testHeadResponse;

+    this.startTime = startTime;

+    this.endTime = endTime;

+  }

+

+  public int getStatusCode(){return statusCode;}

+  public void setStatusCode(int testHeadCode){this.statusCode = statusCode;}

+

+  public ObjectId getTestHeadId() {

+    return testHeadId;

+  }

+

+  public void setTestHeadId(ObjectId testHeadId) {

+    this.testHeadId = testHeadId;

+  }

+

+  public String getTestHeadName() {

+    return testHeadName;

+  }

+

+  public void setTestHeadName(String testHeadName) {

+    this.testHeadName = testHeadName;

+  }

+

+  public ObjectId getTestHeadGroupId() {

+    return testHeadGroupId;

+  }

+

+  public void setTestHeadGroupId(ObjectId testHeadGroupId) {

+    this.testHeadGroupId = testHeadGroupId;

+  }

+

+  public String getBpmnVthTaskId() {

+    return bpmnVthTaskId;

+  }

+

+  public void setBpmnVthTaskId(String bpmnVthTaskId) {

+    this.bpmnVthTaskId = bpmnVthTaskId;

+  }

+

+  public TestHeadRequest getTestHeadRequest() {

+    return testHeadRequest;

+  }

+

+  public void setTestHeadRequest(TestHeadRequest testHeadRequest) {

+    this.testHeadRequest = testHeadRequest;

+  }

+

+  public Map<String, Object> getTestHeadResponse() {

+    return testHeadResponse;

+  }

+

+  public void setTestHeadResponse(Map<String, Object> testHeadResponse) {

+    this.testHeadResponse = testHeadResponse;

+  }

+

+  public Date getStartTime() {

+    return startTime;

+  }

+

+  public void setStartTime(Date startTime) {

+    this.startTime = startTime;

+  }

+

+  public Date getEndTime() {

+    return endTime;

+  }

+

+  public void setEndTime(Date endTime) {

+    this.endTime = endTime;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..b103f1e
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/model/local/UserGroup.java
@@ -0,0 +1,56 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.List;

+import org.bson.types.ObjectId;

+

+public class UserGroup implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId groupId;

+  private List<String> permissions;

+

+  public UserGroup(ObjectId groupId, List<String> permissions) {

+    this.groupId = groupId;

+    this.permissions = permissions;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public List<String> getPermissions() {

+    return permissions;

+  }

+

+  public void setPermissions(List<String> permissions) {

+    this.permissions = permissions;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..1876651
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/repository/GroupRepository.java
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.Group;

+import org.bson.types.ObjectId;

+import org.springframework.data.mongodb.repository.MongoRepository;

+import org.springframework.data.mongodb.repository.Query;

+

+import java.util.List;

+

+public interface GroupRepository extends MongoRepository<Group, String> {

+    @Query("{ 'members.userId': ?0 }")

+    public List<Group> findAllByMembersId(ObjectId membersUserId);

+}

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
index 0000000..fa89a93
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/repository/TestDefinitionRepository.java
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.TestDefinition;

+import java.util.Optional;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public interface TestDefinitionRepository extends MongoRepository<TestDefinition, String> {

+  Optional<TestDefinition> findByProcessDefinitionKey(String processDefinitionKey);

+}

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
index 0000000..5122fea
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/repository/TestExecutionRepository.java
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.TestExecution;

+import java.util.Optional;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public interface TestExecutionRepository extends MongoRepository<TestExecution, String> {

+  Optional<TestExecution> findFirstByProcessInstanceId(String processInstanceId);

+}

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
index 0000000..126d00f
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/repository/TestHeadRepository.java
@@ -0,0 +1,24 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.TestHead;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public interface TestHeadRepository extends MongoRepository<TestHead, String> {

+

+}

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
index 0000000..b073155
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/repository/TestInstanceRepository.java
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.TestInstance;

+import java.util.List;

+import java.util.Optional;

+import org.bson.types.ObjectId;

+import org.springframework.data.mongodb.repository.MongoRepository;

+import org.springframework.data.mongodb.repository.Query;

+

+public interface TestInstanceRepository extends MongoRepository<TestInstance, String> {

+  Optional<TestInstance> findByTestInstanceName(String testInstanceName);

+

+  @Query("{ 'testDefinitionId': ?0 }")

+  List<TestInstance> findAllByTestDefinitionId(ObjectId testDefinitionId);

+

+  @Query("{ 'testDefinitionId': ?0, 'processDefinitionId': ?1 }")

+  List<TestInstance> findAllByTestDefinitionIdAndPDId(

+      ObjectId testDefinitionId, String processDefinitionId);

+}

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
index 0000000..5dd669f
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/repository/UserRepository.java
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.User;

+import java.util.Optional;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public interface UserRepository extends MongoRepository<User, String> {

+  Optional<User> findFirstByEmail(String email);

+}

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
index 0000000..5d7798b
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/Utility.java
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility;

+

+import com.fasterxml.jackson.databind.ObjectMapper;

+import com.google.common.base.Strings;

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.Collection;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.UUID;

+

+public class Utility {

+

+  public static String getLoggerPrefix() {

+    return "[" + Thread.currentThread().getStackTrace()[2].getMethodName() + "]: ";

+  }

+

+  public static Map<?, ?> toMap(Object obj) throws Exception {

+    ObjectMapper mapper = new ObjectMapper();

+    return mapper.convertValue(obj, HashMap.class);

+  }

+

+  public static boolean isCollection(Object obj) {

+    return obj.getClass().isArray() || obj instanceof Collection;

+  }

+

+  public static List<?> toList(Object obj) {

+    if (obj == null) {

+      throw new NullPointerException("Argument cannot be null.");

+    }

+

+    List<?> list = new ArrayList<>();

+    if (obj.getClass().isArray()) {

+      list = Arrays.asList((Object[]) obj);

+    } else if (obj instanceof Collection) {

+      list = new ArrayList<>((Collection<?>) obj);

+    }

+

+    return list;

+  }

+

+  public static boolean isValidUuid(String str) {

+    if (Strings.isNullOrEmpty(str)) {

+      return false;

+    }

+    try {

+      UUID uuid = UUID.fromString(str);

+      return uuid.toString().equalsIgnoreCase(str);

+    } catch (IllegalArgumentException iae) {

+      return false;

+    }

+  }

+}

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
index 0000000..5de5043
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/database/Generic.java
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.database;

+

+import java.util.Optional;

+import org.bson.types.ObjectId;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public class Generic {

+

+  public static <T> boolean identifierExistsInCollection(

+      MongoRepository<T, String> repository, ObjectId identifier) {

+    return repository.findById(identifier.toString()).isPresent();

+  }

+

+  public static <T> T findByIdGeneric(MongoRepository<T, String> repository, ObjectId identifier) {

+    Optional<T> optionalObj = repository.findById(identifier.toString());

+    return optionalObj.orElse(null);

+  }

+

+

+}

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
index 0000000..ba236cb
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/database/TestExecutionUtility.java
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.database;

+

+import org.oran.otf.common.model.TestExecution;

+import com.mongodb.client.result.UpdateResult;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+

+public class TestExecutionUtility {

+

+  public static void saveTestResult(MongoTemplate mongoOperation, TestExecution execution, String testResult){

+    Query query = new Query();

+    query.addCriteria(Criteria.where("businessKey").is(execution.getBusinessKey()));

+    Update update = new Update();

+    update.set("testResult", testResult);

+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+  }

+}

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
index 0000000..fb1e55f
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/gson/Convert.java
@@ -0,0 +1,121 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.gson;

+

+import com.fasterxml.jackson.core.type.TypeReference;

+import com.fasterxml.jackson.databind.DeserializationFeature;

+import com.fasterxml.jackson.databind.ObjectMapper;

+import com.google.gson.Gson;

+import com.google.gson.GsonBuilder;

+import com.google.gson.JsonDeserializationContext;

+import com.google.gson.JsonDeserializer;

+import com.google.gson.JsonElement;

+import com.google.gson.JsonParseException;

+import com.google.gson.JsonPrimitive;

+import com.google.gson.JsonSerializationContext;

+import com.google.gson.JsonSerializer;

+import com.google.gson.reflect.TypeToken;

+

+import java.io.IOException;

+import java.lang.reflect.Type;

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.Collection;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import org.bson.types.ObjectId;

+

+public class Convert {

+

+  private static final GsonBuilder gsonBuilder =

+      new GsonBuilder()

+          .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")

+          .registerTypeAdapter(

+              ObjectId.class,

+              new JsonSerializer<ObjectId>() {

+                @Override

+                public JsonElement serialize(

+                    ObjectId src, Type typeOfSrc, JsonSerializationContext context) {

+                  return new JsonPrimitive(src.toHexString());

+                }

+              })

+          .registerTypeAdapter(

+              ObjectId.class,

+              new JsonDeserializer<ObjectId>() {

+                @Override

+                public ObjectId deserialize(

+                    JsonElement json, Type typeOfT, JsonDeserializationContext context)

+                    throws JsonParseException {

+                  return new ObjectId(json.getAsString());

+                }

+              });

+

+  public static Gson getGson() {

+    return gsonBuilder.create();

+  }

+

+  public static String mapToJson(Map map) {

+    if (map.isEmpty()) {

+      return "{}";

+    }

+    return getGson().toJson(map);

+  }

+

+  public static String listToJson(List list) {

+    if (list.isEmpty()) {

+      return "[]";

+    }

+    return getGson().toJson(list);

+  }

+

+  public static Map<String, Object> jsonToMap(String json) {

+    Type type = new TypeToken<HashMap<String, Object>>() {

+    }.getType();

+    return getGson().fromJson(json, type);

+  }

+

+  public static String objectToJson(Object obj) {

+    return getGson().toJson(obj);

+  }

+

+  public static <T> T jsonToObject(String json, TypeReference<T> typeReference) throws IOException {

+    ObjectMapper objectMapper = new ObjectMapper();

+    objectMapper

+            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);

+    return objectMapper.readValue(json, typeReference);

+  }

+

+  public static<T> T mapToObject(Map map, TypeReference<T> typeReference) throws IOException {

+    return jsonToObject(mapToJson(map), typeReference);

+  }

+

+  public static<T> T listToObjectList(List list, TypeReference<T> typeReference) throws IOException {

+    return jsonToObject(listToJson(list), typeReference);

+  }

+

+  public static List<?> convertObjectToList(Object obj) {

+    List<?> list = new ArrayList<>();

+    if (obj.getClass().isArray()) {

+      list = Arrays.asList((Object[])obj);

+    } else if (obj instanceof Collection) {

+      list = new ArrayList<>((Collection<?>)obj);

+    }

+    return list;

+  }

+

+}

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
index 0000000..be6ae88
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/http/HeadersUtility.java
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.http;

+

+import com.google.gson.Gson;

+import java.util.Map;

+

+public class HeadersUtility {

+  public static Map<String, String> maskAuth(Map<String, String> headers){

+    //Deep copy headers to avoid changing original

+    Gson gson = new Gson();

+    String jsonString = gson.toJson(headers);

+    Map<String, String> maskedHeaders = gson.fromJson(jsonString, Map.class);

+

+    if(maskedHeaders.containsKey("Authorization")) {

+      String[] auth = maskedHeaders.get("Authorization").split(" ");

+      if(auth.length>1) {

+        auth[1] = "****";

+        maskedHeaders.put("Authorization", auth[0] + " " + auth[1]);

+      }

+      else{

+        maskedHeaders.put("Authorization", "****");

+      }

+    }

+    return maskedHeaders;

+  }

+

+}

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
index 0000000..8cd2eff
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/http/RequestUtility.java
@@ -0,0 +1,195 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.http;

+

+import com.google.common.base.Strings;

+import java.io.UnsupportedEncodingException;

+import java.util.HashMap;

+import java.util.Map;

+import java.util.Timer;

+import java.util.TimerTask;

+import java.util.concurrent.Future;

+import org.apache.http.HttpHost;

+import org.apache.http.HttpResponse;

+import org.apache.http.client.config.RequestConfig;

+import org.apache.http.client.methods.HttpGet;

+import org.apache.http.client.methods.HttpPost;

+import org.apache.http.client.methods.HttpRequestBase;

+import org.apache.http.client.protocol.HttpClientContext;

+import org.apache.http.entity.StringEntity;

+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;

+import org.apache.http.impl.nio.client.HttpAsyncClients;

+import org.apache.http.protocol.BasicHttpContext;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

+public class RequestUtility {

+

+  private static final Logger logger = LoggerFactory.getLogger(RequestUtility.class);

+

+  public static void postAsync(String url, String body, Map<String, String> headers, Boolean proxy)

+      throws Exception {

+    HttpPost post = buildPost(url, body, headers);

+    executeAsync(post, proxy);

+  }

+

+  public static HttpResponse postSync(

+      String url, String body, Map<String, String> headers, Boolean proxy) throws Exception {

+    HttpPost post = buildPost(url, body, headers);

+    return executeSync(post, proxy);

+  }

+

+  public static HttpResponse postSync(

+      String url, String body, Map<String, String> headers, int timeoutInMillis, Boolean proxy)

+      throws Exception {

+    HttpPost post = buildPost(url, body, headers);

+    return executeSync(post, timeoutInMillis, proxy);

+  }

+

+  public static HttpResponse getSync(String url, Map<String, String> headers, Boolean proxy)

+      throws Exception {

+    HttpGet get = buildGet(url, headers);

+    return executeSync(get, proxy);

+  }

+

+  public static HttpResponse getSync(

+      String url, Map<String, String> headers, int timeoutInMillis, Boolean proxy)

+      throws Exception {

+    if (timeoutInMillis < 0) {

+      throw new IllegalArgumentException("The timeoutInMillis must be a value greater than 0.");

+    }

+

+    HttpGet get = buildGet(url, headers);

+    return executeSync(get, timeoutInMillis, proxy);

+  }

+

+  public static void getAsync(String url, Map<String, String> headers, Boolean proxy)

+      throws Exception {

+    HttpGet get = buildGet(url, headers);

+    executeAsync(get, proxy);

+  }

+

+  private static HttpPost buildPost(String url, String body, Map<String, String> headers)

+      throws UnsupportedEncodingException {

+    if (Strings.isNullOrEmpty(url) || Strings.isNullOrEmpty(body)) {

+      return null;

+    } else if (headers == null) {

+      headers = new HashMap<>();

+    }

+

+    HttpPost post = new HttpPost(url);

+    headers.forEach(post::setHeader);

+    post.setEntity(new StringEntity(body));

+    return post;

+  }

+

+  private static HttpGet buildGet(String url, Map<String, String> headers) {

+    if (Strings.isNullOrEmpty(url)) {

+      return null;

+    } else if (headers == null) {

+      headers = new HashMap<>();

+    }

+

+    HttpGet get = new HttpGet(url);

+    headers.forEach(get::setHeader);

+    return get;

+  }

+

+  private static HttpResponse executeSync(HttpRequestBase request, Boolean proxy) throws Exception {

+    CloseableHttpAsyncClient httpClient = createHttpAsyncClient();

+    try {

+      httpClient.start();

+      Future<HttpResponse> future =

+          proxy

+              ? httpClient.execute(request, createHttpClientContext(), null)

+              : httpClient.execute(request, null);

+      return future.get();

+    } catch (Exception e) {

+      throw e;

+    } finally {

+      httpClient.close();

+    }

+  }

+

+  private static HttpResponse executeSync(

+      HttpRequestBase request, int timeoutInMillis, Boolean proxy) throws Exception {

+    if (timeoutInMillis < 0) {

+      throw new IllegalArgumentException("The timeoutInMillis must be a value greater than 0.");

+    }

+

+    // Create a timer task that will abort the task (the request) after the specified time. This

+    // task will run *timeoutInMillis* ms

+    TimerTask task =

+        new TimerTask() {

+          @Override

+          public void run() {

+            if (request != null) {

+              request.abort();

+            }

+          }

+        };

+

+    CloseableHttpAsyncClient httpClient = createHttpAsyncClient();

+    try {

+      httpClient.start();

+      // Start the timer before making the request.

+      new Timer(true).schedule(task, timeoutInMillis);

+      Future<HttpResponse> future =

+          proxy

+              ? httpClient.execute(request, createHttpClientContext(), null)

+              : httpClient.execute(request, null);

+

+      return future.get();

+    } catch (Exception e) {

+      throw e;

+    } finally {

+      httpClient.close();

+    }

+  }

+

+  private static void executeAsync(HttpRequestBase request, Boolean proxy) throws Exception {

+    CloseableHttpAsyncClient httpClient = createHttpAsyncClient();

+    try {

+      httpClient.start();

+      Future<HttpResponse> future =

+          proxy

+              ? httpClient.execute(request, createHttpClientContext(), null)

+              : httpClient.execute(request, null);

+      logger.debug("Sent asynchronous request.");

+    } catch (Exception e) {

+      throw e;

+    } finally {

+      httpClient.close();

+    }

+  }

+

+  private static RequestConfig configureProxy() {

+    HttpHost proxy;

+    proxy = new HttpHost("localhost", 8080, "http");

+    return RequestConfig.custom().setProxy(proxy).build();

+  }

+

+  private static HttpClientContext createHttpClientContext() {

+    HttpClientContext localContext = HttpClientContext.adapt(new BasicHttpContext());

+    localContext.setRequestConfig(configureProxy());

+    return localContext;

+  }

+

+  private static CloseableHttpAsyncClient createHttpAsyncClient() throws Exception {

+    return HttpAsyncClients.createDefault();

+  }

+}

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
index 0000000..abe1e0b
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/http/ResponseUtility.java
@@ -0,0 +1,107 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.http;

+

+import org.oran.otf.common.model.local.OTFApiResponse;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+

+public class ResponseUtility {

+

+  public static class Build {

+

+    public static Response okRequest() {

+      return Response.ok().build();

+    }

+

+    public static Response badRequest() {

+      return Response.status(400).build();

+    }

+

+    public static Response okRequestWithMessage(String msg) {

+      return Response.status(200)

+              .type(MediaType.APPLICATION_JSON)

+              .entity(new OTFApiResponse(200, msg))

+              .build();

+    }

+

+    public static Response okRequestWithObject(Object obj) {

+      return Response.status(200)

+              .type(MediaType.APPLICATION_JSON)

+              .entity(obj)

+              .build();

+    }

+

+    public static Response badRequestWithMessage(String msg) {

+      return Response.status(400)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(400, msg))

+          .build();

+    }

+

+    public static Response internalServerError() {

+      return Response.status(500).build();

+    }

+

+    public static Response internalServerErrorWithMessage(String msg) {

+      return Response.status(500)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(500, msg))

+          .build();

+    }

+

+    public static Response unauthorized() {

+      return Response.status(401).build();

+    }

+

+    public static Response unauthorizedWithMessage(String msg) {

+      return Response.status(401)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(401, msg))

+          .build();

+    }

+

+    public static Response notFound() {

+      return Response.status(404).build();

+    }

+

+    public static Response notFoundWithMessage(String msg) {

+      return Response.status(404)

+              .type(MediaType.APPLICATION_JSON)

+              .entity(new OTFApiResponse(404, msg))

+              .build();

+    }

+

+    public static Response genericWithCode(int code) {

+      return Response.status(code).build();

+    }

+

+    public static Response genericWithMessage(int code, String msg) {

+      return Response.status(code)

+              .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(code, msg))

+              .build();

+    }

+

+    public static Response genericWithObject(int code, Object obj) {

+      return Response.status(code)

+              .type(MediaType.APPLICATION_JSON)

+              .entity(obj)

+              .build();

+    }

+  }

+}

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
index 0000000..8327a81
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/logger/ErrorCode.java
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.logger;

+

+public enum ErrorCode {

+  PermissionError(100),

+  AvailabilityError(200),

+  DataError(300),

+  SchemaError(400),

+  BusinessProcesssError(500),

+  UnknownError(900);

+

+  private int value;

+

+  ErrorCode(int value) {

+    this.value = value;

+  }

+

+  public int getValue() {

+    return this.value;

+  }

+}

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
index 0000000..10c45d8
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/logger/LoggerStartupListener.java
@@ -0,0 +1,91 @@
+/*-

+ * ============LICENSE_START=======================================================

+ * ONAP - SO

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.

+ * ================================================================================

+ * Modifications Copyright (c) 2019 Samsung

+ * ================================================================================

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ * ============LICENSE_END=========================================================

+ */

+

+package org.oran.otf.common.utility.logger;

+

+import ch.qos.logback.classic.Level;

+import ch.qos.logback.classic.LoggerContext;

+import ch.qos.logback.classic.spi.LoggerContextListener;

+import ch.qos.logback.core.Context;

+import ch.qos.logback.core.spi.ContextAwareBase;

+import ch.qos.logback.core.spi.LifeCycle;

+import java.net.InetAddress;

+import java.net.UnknownHostException;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.stereotype.Component;

+

+@Component

+public class LoggerStartupListener extends ContextAwareBase

+    implements LoggerContextListener, LifeCycle {

+

+  private static final Logger logger = LoggerFactory.getLogger(LoggerStartupListener.class);

+  private boolean started = false;

+

+  @Override

+  public void start() {

+    if (started) {

+      return;

+    }

+    InetAddress addr = null;

+    try {

+      addr = InetAddress.getLocalHost();

+    } catch (UnknownHostException e) {

+      logger.error("UnknownHostException", e);

+    }

+    Context context = getContext();

+    if (addr != null) {

+      context.putProperty("server.name", addr.getHostName());

+    }

+    started = true;

+  }

+

+  @Override

+  public void stop() {

+  }

+

+  @Override

+  public boolean isStarted() {

+    return started;

+  }

+

+  @Override

+  public boolean isResetResistant() {

+    return true;

+  }

+

+  @Override

+  public void onReset(LoggerContext arg0) {

+  }

+

+  @Override

+  public void onStart(LoggerContext arg0) {

+  }

+

+  @Override

+  public void onStop(LoggerContext arg0) {

+  }

+

+  @Override

+  public void onLevelChange(ch.qos.logback.classic.Logger logger, Level level) {

+  }

+}

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
index 0000000..1103c53
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/logger/MessageEnum.java
@@ -0,0 +1,35 @@
+/*-

+ * ============LICENSE_START=======================================================

+ * ONAP - SO

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.

+ * ================================================================================

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ * ============LICENSE_END=========================================================

+ */

+

+package org.oran.otf.common.utility.logger;

+

+

+public enum MessageEnum {

+  // Api Handler Messages

+  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,

+  // Resource Adapter Messages

+  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,

+  // BPEL engine Messages

+  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,

+  // ASDC Messages

+  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,

+  // Default Messages, in case Log catalog is not defined

+  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

+}

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
index 0000000..0bbab4a
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/PermissionChecker.java
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.permissions;

+

+import org.oran.otf.common.model.Group;

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.repository.GroupRepository;

+

+import java.util.Collection;

+

+public class PermissionChecker {

+    //check is a user have a certain permission in a group

+    public static boolean hasPermissionTo(User user, Group group, String permission, GroupRepository groupRepository){

+        UserPermission userPermission = new PermissionUtil().buildUserPermission(user,groupRepository);

+        return hasPermissionTo(userPermission,group,permission);

+    }

+    public static boolean hasPermissionTo(User user, Group group, Collection<String> permissions, GroupRepository groupRepository){

+        UserPermission userPermission = new PermissionUtil().buildUserPermission(user,groupRepository);

+        for(String permission : permissions){

+            if(!hasPermissionTo(userPermission,group,permission)){

+                return false;

+            }

+        }

+        return true;

+    }

+    // check a users list of permission in a group

+    private static boolean hasPermissionTo(UserPermission userPermission, Group group, String permission){

+        switch (permission.toUpperCase()) {

+            case (UserPermission.Permission.READ):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.READ);

+            case (UserPermission.Permission.WRITE):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.WRITE);

+            case (UserPermission.Permission.EXECUTE):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.EXECUTE);

+            case (UserPermission.Permission.DELETE):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.DELETE);

+            case (UserPermission.Permission.MANAGEMENT):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.MANAGEMENT);

+            default:

+                return false;// reaches here when permission provided is not an option

+        }

+    }

+}
\ 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
index 0000000..2a180bc
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/PermissionUtil.java
@@ -0,0 +1,238 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.permissions;

+

+import org.oran.otf.common.model.Group;

+import org.oran.otf.common.model.GroupMember;

+import org.oran.otf.common.model.Role;

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.repository.GroupRepository;

+

+import java.util.*;

+

+public class PermissionUtil {

+    //build userPermission object which contains all access control information of the user

+    public UserPermission buildUserPermission(User user, GroupRepository groupRepository) {

+        UserPermission userPermission = new UserPermission();

+        userPermission.setUser(user);

+        Map<String,Set<String>> userAccessMap; // map from group to permission that user have in that group

+

+        userAccessMap = mapGroupsToPermission(user,groupRepository);

+        userPermission.setUserAccessMap(userAccessMap);

+        return userPermission;

+    }

+    // return if user have specified permission in a certain group

+    // ***********only use this on groups that the user is in directly (non-child and non parents)****************

+    public static boolean hasDirectPermissionTo(String permission, User user, Group group) {

+        Set<String> possiblePermissions= getUserGroupPermissions(user,group);

+        return possiblePermissions.stream().anyMatch(p-> p.equalsIgnoreCase(permission)); //

+    }

+    // Get all the permissions the user have in a certain group

+    public static Set<String> getUserGroupPermissions(User user, Group group){

+        Set<String> permissionsAllowed = new HashSet<>();

+        Set<String> usersAssignedRoles = findUserRoles(user,group);

+        if(usersAssignedRoles.isEmpty()) // empty set permissions because the user have no roles in the group aka not a member

+            return permissionsAllowed;

+        //get every single permissions for each role that the user have.

+        for(String role : usersAssignedRoles){

+             permissionsAllowed.addAll(getRolePermissions(role,group));

+        }

+        return permissionsAllowed;

+    }

+    //get the permissions associated with the userRoleName in group

+    public static Set<String> getRolePermissions(String userRoleName, Group group)

+    {

+        for(Role role : group.getRoles())

+        {

+            if(role.getRoleName().equalsIgnoreCase(userRoleName))

+            {

+                return new HashSet<String>(role.getPermissions());

+            }

+        }

+        return new HashSet<String>(); // empty string set if the role name cant be found in the group

+    }

+    // find the user's role in the specified group

+    public static Set<String> findUserRoles(User user, Group group){

+        for(GroupMember member : group.getMembers())

+        {

+            // if userId matches then get all the user's role in the group

+            if(member.getUserId().toString().equals(user.get_id().toString()))

+                return new HashSet<String>(member.getRoles());

+        }

+        return new HashSet<String>(); //if user have no roles

+    }

+

+    // create map that where key is the group id and value = users permission (string) that that group

+    private Map<String,Set<String>> mapGroupsToPermission(User user, GroupRepository groupRepository){

+        Map<String,Set<String>> groupAccessMap = new HashMap<>();

+        List<Group> enrolledGroups = groupRepository.findAllByMembersId(user.get_id());// enrolledGroups = groups that user is a member of

+        Map<String, Group> allGroupMap = groupListToMap(groupRepository.findAll());

+        // get all permission in the groups the user is ia member of

+        for(Group group: enrolledGroups) {

+            Set<String> permissions = getUserGroupPermissions(user,group);

+            groupAccessMap.put(group.get_id().toString(),convertPermissions(permissions));

+        }

+        //assign add read to all parent groups

+        Set<String> parentGroupsId = getParentGroups(enrolledGroups,allGroupMap);

+        for(String parentId : parentGroupsId)

+        {

+            // if parent access role already exist in

+            // group access map cause they are a member

+            if(groupAccessMap.get(parentId)!= null)

+                groupAccessMap.get(parentId).add(UserPermission.Permission.READ);

+            else

+                groupAccessMap.put(parentId,new HashSet<String>(Arrays.asList(UserPermission.Permission.READ)));

+        }

+        // if there is management role

+        // then assign read access to children

+        if(hasManagementRole(user,enrolledGroups)){

+//            Set<String>childIds = getChildrenGroupsId(enrolledGroups,allGroupMap,user);

+            for(Group enrolledGroup : enrolledGroups) {

+                // if enrolled groups is a management group

+                if(hasDirectPermissionTo(UserPermission.Permission.MANAGEMENT,user,enrolledGroup)){

+                    // if there is management role then get all the child of that group, do this for all management groups

+                    Set<String> childIds= getChildrenGroupsId(Arrays.asList(enrolledGroup),allGroupMap,user);

+                    Set<String> userGroupPermissions = convertPermissions(getUserGroupPermissions(user,enrolledGroup));

+                    for(String childId : childIds){

+                        if (groupAccessMap.get(childId) != null)

+                            groupAccessMap.get(childId).addAll(userGroupPermissions);

+                        else{

+                            groupAccessMap.put(childId,userGroupPermissions);

+                        }

+                    }

+                }

+            }

+        }

+        return groupAccessMap;

+    }

+    // check is user have managementRole

+    private boolean hasManagementRole(User user, List<Group> enrolledGroups)

+    {

+        for(Group group: enrolledGroups){

+            if(hasDirectPermissionTo(UserPermission.Permission.MANAGEMENT,user,group))

+            {

+                return true;

+            }

+        }

+        return false;

+    }

+    // get the parent groups starting from the enrolled group of the user

+    private Set<String> getParentGroups(List<Group> enrolledGroup, Map<String, Group> groupMap )

+    {

+        Set<String> parentGroups = new HashSet<>();

+        return lookUp(enrolledGroup,groupMap,parentGroups);

+    }

+    //recursive lookup starting at the enrolled groups that the user is a member of

+    private Set<String> lookUp(List<Group> groupsToCheck, Map<String, Group> groupMap, Set<String> resultSet)

+    {

+        //base case: nothing to check anymore

+        if(groupsToCheck.isEmpty())

+            return resultSet;

+        //This is the parents directly above the current groups that are being checked

+        List<Group> currentParentGroups = new ArrayList<>();

+

+        for(Group group : groupsToCheck)

+        {

+            if(group.getParentGroupId() != null) // if there is a parent

+            {

+                String parentId = group.getParentGroupId().toString();

+                Group parentGroup = groupMap.get(parentId);

+                resultSet.add(parentId);

+                currentParentGroups.add(parentGroup); // add to currentParentGroup so it can be used recursively check for more parents

+            }

+        }

+        return lookUp(currentParentGroups,groupMap,resultSet);

+    }

+    // convert a list of groups to a map of group ids to group

+    private Map<String, Group> groupListToMap(List<Group> allGroups)

+    {

+        Map<String, Group> groupMap = new HashMap<>();

+        allGroups.forEach(group -> groupMap.put(group.get_id().toString(),group));

+        return groupMap;

+    }

+    //get all the child group

+    private Set<String> getChildrenGroupsId(List<Group> enrolledGroup, Map<String, Group> allGroupsMap, User user)

+    {

+        Set<String> childrenGroups = new HashSet<>();

+        Set<String> managementGroupIds = getManagementGroupIds(enrolledGroup,user);

+        return  lookForChildren(managementGroupIds,allGroupsMap,childrenGroups);

+    }

+

+    private Set<String> getManagementGroupIds(List<Group> enrolledGroups, User user)

+    {

+        Set<String> parentIds = new HashSet<>();

+        for(Group group: enrolledGroups)

+        {

+            if(hasDirectPermissionTo(UserPermission.Permission.MANAGEMENT,user,group)) // has Management permission

+            {

+                parentIds.add(group.get_id().toString());

+            }

+        }

+        return parentIds;

+    }

+    //recursive look down for childrens via breath first search

+    private Set<String> lookForChildren (Set<String> parentIds, Map<String, Group> allGroupsMap, Set<String> resultSet)

+    {

+        //base case = no groups to check anymore;

+        if (parentIds.isEmpty())

+            return resultSet;

+

+        Set<String> currentChildrenIds = new HashSet<>();

+        for(String groupId : allGroupsMap.keySet())

+        {

+            Group possibleChildGroup = allGroupsMap.get(groupId);

+            if(isChildOf(parentIds,possibleChildGroup)) // if parent id is the same

+            {

+                currentChildrenIds.add(groupId);

+                resultSet.add(groupId);

+            }

+        }

+        return lookForChildren(currentChildrenIds,allGroupsMap,resultSet);

+    }

+    //check if a group is a child of a list of parent group ids

+    private boolean isChildOf(Set<String>parentGroupIds, Group childGroup){

+        for(String parentId: parentGroupIds)

+        {

+            if(isChildOf(parentId,childGroup))

+                return true;

+        }

+        return false;

+    }

+    //check is group has parent that is specified by parentId

+    private boolean isChildOf(String parentId, Group childGroup) {

+        if(childGroup.getParentGroupId() == null)

+            return false;

+       return childGroup.getParentGroupId().toString().equals(parentId);

+    }

+

+    private Set<String> convertPermissions (Set<String> permissions){

+        Set<String> result = new HashSet<>();

+        for (String permission: permissions){

+            if(permission.equalsIgnoreCase(UserPermission.Permission.READ))

+                result.add(UserPermission.Permission.READ);

+            else if (permission.equalsIgnoreCase(UserPermission.Permission.WRITE))

+                result.add(UserPermission.Permission.WRITE);

+            else if (permission.equalsIgnoreCase(UserPermission.Permission.DELETE))

+                result.add(UserPermission.Permission.DELETE);

+            else if (permission.equalsIgnoreCase(UserPermission.Permission.EXECUTE))

+                result.add(UserPermission.Permission.EXECUTE);

+            else if (permission.equalsIgnoreCase(UserPermission.Permission.MANAGEMENT))

+                result.add(UserPermission.Permission.MANAGEMENT);

+        }

+            return result;

+    }

+}

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
index 0000000..1883721
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/permissions/UserPermission.java
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.permissions;

+

+import org.oran.otf.common.model.User;

+

+import java.util.Map;

+import java.util.Set;

+

+public class UserPermission {

+    private User user;

+    private Map<String,Set<String>> userAccessMap;

+

+    public User getUser() {

+        return user;

+    }

+

+    public void setUser(User user) {

+        this.user = user;

+    }

+

+    public Map<String, Set<String>> getUserAccessMap() {

+        return userAccessMap;

+    }

+

+    public void setUserAccessMap(Map<String,Set<String>> userAccessMap) {

+        this.userAccessMap = userAccessMap;

+    }

+

+    public boolean  hasAccessTo(String groupId,String permission) {

+        if (userAccessMap.get(groupId) == null) {

+            return false;

+        }

+        Set<String> group = userAccessMap.get(groupId);

+        return group.stream().anyMatch(groupPermission->groupPermission.equalsIgnoreCase(permission));

+    }

+    public class Permission{

+        public static final String READ = "READ";

+        public static final String WRITE = "WRITE";

+        public static final String EXECUTE = "EXECUTE";

+        public static final String DELETE = "DELETE";

+        public static final String MANAGEMENT ="MANAGEMENT";

+    }

+}

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
index 0000000..cdaf8fd
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/common/utility/sftp/SftpUtility.java
@@ -0,0 +1,94 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.sftp;

+

+import org.apache.commons.io.IOUtils;

+import org.apache.commons.vfs2.*;

+import org.apache.commons.vfs2.provider.sftp.IdentityInfo;

+import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;

+

+import java.io.File;

+import java.io.InputStream;

+

+

+public class SftpUtility {

+

+    public static byte[] getFile(String host, String artifactPath, String privateKeyPath, String privateKeyUsername, String privateKeyPasspharase) throws Exception {

+        String remoteURI = "sftp://" + privateKeyUsername + "@" + host + "/" + artifactPath;

+

+        FileSystemOptions fsOptions = new FileSystemOptions();

+        FileSystemManager fsManager = null;

+        byte[] bytes = null;

+        SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();

+        builder.setUserDirIsRoot(fsOptions, false);

+        builder.setStrictHostKeyChecking(fsOptions, "no");

+        IdentityInfo identityInfo = new IdentityInfo(new File(privateKeyPath), privateKeyPasspharase.getBytes());

+        builder.setIdentityInfo(fsOptions, identityInfo);

+        fsManager = VFS.getManager();

+        FileObject remoteFileObject = fsManager.resolveFile(remoteURI, fsOptions);

+        if(!remoteFileObject.isFile()) {

+            remoteFileObject.close();

+            throw new Exception("Expected a file, but supplied filePath was not a file.");

+        }

+        InputStream is = remoteFileObject.getContent().getInputStream();

+        bytes = IOUtils.toByteArray(is);

+        remoteFileObject.close();

+        return bytes;

+

+    }

+

+    public static FileObject getDirectory(String host, String artifactPath, String privateKeyPath, String privateKeyUsername, String privateKeyPasspharase) throws Exception {

+        String remoteURI = "sftp://" + privateKeyUsername + "@" + host + "/" + artifactPath;

+

+        FileSystemOptions fsOptions = new FileSystemOptions();

+        FileSystemManager fsManager = null;

+        SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();

+        builder.setUserDirIsRoot(fsOptions, false);

+        builder.setStrictHostKeyChecking(fsOptions, "no");

+        IdentityInfo identityInfo = new IdentityInfo(new File(privateKeyPath), privateKeyPasspharase.getBytes());

+        builder.setIdentityInfo(fsOptions, identityInfo);

+        fsManager = VFS.getManager();

+        FileObject fileObject = fsManager.resolveFile(remoteURI, fsOptions);

+        if(fileObject.isFolder()) {

+            return fileObject;

+        }

+        fileObject.close();

+        throw new Exception("Expected a folder, but supplied filePath was not a folder.");

+    }

+

+    public static void uploadFile(String host, String artifactPath, String privateKeyPath, String privateKeyUsername, String privateKeyPasspharase, File tempFile) throws Exception {

+        String remoteURI = "sftp://" + privateKeyUsername + "@" + host + "/" + artifactPath;

+

+        FileSystemOptions fsOptions = new FileSystemOptions();

+        FileSystemManager fsManager = null;

+        SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();

+        builder.setUserDirIsRoot(fsOptions, false);

+        builder.setStrictHostKeyChecking(fsOptions, "no");

+        IdentityInfo identityInfo = new IdentityInfo(new File(privateKeyPath), privateKeyPasspharase.getBytes());

+        builder.setIdentityInfo(fsOptions, identityInfo);

+        fsManager = VFS.getManager();

+        //resolve file location

+        FileObject remoteFileObject = fsManager.resolveFile(remoteURI, fsOptions);

+        FileObject sourceFile = fsManager.resolveFile(tempFile.getAbsolutePath());

+        //if file exists then override, else create file

+        remoteFileObject.copyFrom(sourceFile, Selectors.SELECT_SELF);

+        remoteFileObject.close();

+        sourceFile.close();

+    }

+

+

+}

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
index 0000000..0d21acf
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/event/TestInstanceCompletionEvent.java
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.event;

+

+import com.google.gson.JsonObject;

+import org.camunda.bpm.engine.delegate.DelegateExecution;

+import org.springframework.context.ApplicationEvent;

+

+public class TestInstanceCompletionEvent extends ApplicationEvent {

+

+  private JsonObject obj;

+  private DelegateExecution execution;

+

+  public TestInstanceCompletionEvent(Object source, JsonObject obj, DelegateExecution execution) {

+    super(source);

+    this.obj = obj;

+    this.execution = execution;

+  }

+

+  public JsonObject getObj() {

+    return obj;

+  }

+

+  public void setObj(JsonObject obj) {

+    this.obj = obj;

+  }

+

+  public DelegateExecution getExecution() {

+    return execution;

+  }

+

+  public void setExecution(DelegateExecution execution) {

+    this.execution = execution;

+  }

+}

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
index 0000000..0bb5eb6
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/event/TestInstanceIncidentEvent.java
@@ -0,0 +1,59 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.event;

+

+import org.camunda.bpm.engine.impl.incident.IncidentContext;

+import org.springframework.context.ApplicationEvent;

+

+public class TestInstanceIncidentEvent extends ApplicationEvent {

+

+  private IncidentContext context;

+  private String message;

+  private String type;

+

+  public TestInstanceIncidentEvent(Object source, IncidentContext context, String message,

+      String type) {

+    super(source);

+    this.context = context;

+    this.message = message;

+    this.type = type;

+  }

+

+  public IncidentContext getContext() {

+    return context;

+  }

+

+  public void setContext(IncidentContext context) {

+    this.context = context;

+  }

+

+  public String getMessage() {

+    return message;

+  }

+

+  public void setMessage(String message) {

+    this.message = message;

+  }

+

+  public String getType() {

+    return type;

+  }

+

+  public void setType(String type) {

+    this.type = type;

+  }

+}

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
index 0000000..d627f31
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/DeleteProcessInstanceService.java
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service;

+

+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

+

+import javax.ws.rs.DELETE;

+import javax.ws.rs.Path;

+import javax.ws.rs.PathParam;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.Response;

+

+@Path("/tcu")

+public interface DeleteProcessInstanceService {

+

+    @DELETE

+    @Path("/delete-process-instance/v1/{executionId}")

+    @Produces(APPLICATION_JSON)

+    Response deleteProcessInstance(@PathParam("executionId") String executionId);

+}

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
index 0000000..b78d23d
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/DeleteTestDefinitionService.java
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service;

+

+import javax.ws.rs.*;

+import javax.ws.rs.core.Response;

+

+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

+

+@Path("/tcu")

+public interface DeleteTestDefinitionService {

+

+    @DELETE

+    @Path("/delete-test-strategy/v1/deployment-id/{deploymentId}/")

+    @Consumes(APPLICATION_JSON)

+    @Produces(APPLICATION_JSON)

+    Response deleteTestStrategyByDeploymentId(@PathParam("deploymentId") String deploymentId);

+

+

+

+    @DELETE

+    @Path("/delete-test-strategy/v1/test-definition-id/{testDefinitionId}/")

+    @Consumes(APPLICATION_JSON)

+    @Produces(APPLICATION_JSON)

+    public Response deleteTestStrategyByTestDefinitionId(@PathParam("testDefinitionId") String testDefinitionId);

+

+

+

+//    @DELETE

+//    @Path("/delete-all-test-strategies/v1/")

+//    @Consumes(APPLICATION_JSON)

+//    @Produces(APPLICATION_JSON)

+//    Response deleteAllTestStrategies();

+}

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
index 0000000..3e6d036
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/DeveloperService.java
@@ -0,0 +1,80 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service;

+

+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

+

+import javax.servlet.http.HttpServletRequest;

+import javax.ws.rs.Consumes;

+import javax.ws.rs.GET;

+import javax.ws.rs.POST;

+import javax.ws.rs.Path;

+import javax.ws.rs.PathParam;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.Context;

+import javax.ws.rs.core.Response;

+

+@Path("/tcu")

+public interface DeveloperService {

+  @POST

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/dev/workflowTaskCleanup/v1/{enabled}")

+  Response workflowTaskCleanup(@PathParam("enabled") String enabled);

+

+  @POST

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/dev/externalTaskWorker/v1/{enabled}")

+  Response externalTaskWorker(@PathParam("enabled") String enabled);

+

+  @GET

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/dev/printThreads/v1")

+  Response printThreads(@Context HttpServletRequest request);

+

+  @POST

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/dev/jobExecutor/v1/activate")

+  Response activateJobExecutor();

+

+  @POST

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/dev/jobExecutor/v1/deactivate")

+  Response deActivateJobExecutor();

+

+  @POST

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/dev/gracefulshutdown/v1")

+  Response gracefulShutdown();

+

+  @POST

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/dev/disableGracefulShutdown/v1")

+  Response disableGracefulShutdown();

+

+  @POST

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/dev/enableGracefulShutdown/v1")

+  Response enableGracefulShutdown();

+}

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
index 0000000..065d886
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/HealthService.java
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service;

+

+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

+

+import javax.ws.rs.Consumes;

+import javax.ws.rs.GET;

+import javax.ws.rs.Path;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.Response;

+

+@Path("/") // Base path for unauthenticated services

+public interface HealthService {

+

+  @GET

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/health/v1")

+  Response getHealth();

+}

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
index 0000000..9d5e575
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/ProcessInstanceCompletionService.java
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service;

+

+

+

+import javax.ws.rs.GET;

+import javax.ws.rs.Path;

+import javax.ws.rs.PathParam;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.Response;

+

+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

+

+@Path("/tcu")

+public interface ProcessInstanceCompletionService {

+

+    @GET

+    @Produces(APPLICATION_JSON)

+    @Path("/process-instance-completion-check/v1/{id}")

+    public Response isProcessInstanceComplete(@PathParam("id") String processInstanceId);

+

+}

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
index 0000000..e6ed4fc
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/TestControlUnitService.java
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service;

+

+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

+

+import javax.ws.rs.Consumes;

+import javax.ws.rs.POST;

+import javax.ws.rs.Path;

+import javax.ws.rs.PathParam;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.Context;

+import javax.ws.rs.core.HttpHeaders;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.MultivaluedMap;

+import javax.ws.rs.core.Response;

+import org.springframework.web.bind.annotation.RequestHeader;

+

+@Path("/tcu")

+public interface TestControlUnitService {

+

+  @POST

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/execute/testInstanceId/{testInstanceId}")

+  Response executeByTestInstanceId(@PathParam("testInstanceId") String testInstanceId);

+

+  @POST

+  @Consumes(APPLICATION_JSON)

+  @Produces(APPLICATION_JSON)

+  @Path("/execute/workflowRequest")

+  Response executeByWorkflowRequest(String workflowRequest);

+}

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
index 0000000..e329652
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/TestDefinitionDeploymentService.java
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service;

+

+

+import org.glassfish.jersey.media.multipart.FormDataParam;

+

+import javax.ws.rs.*;

+import javax.ws.rs.core.Response;

+

+import java.io.InputStream;

+

+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;

+import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA;

+

+@Path("/tcu")

+public interface TestDefinitionDeploymentService {

+

+    @POST

+    @Path("/deploy-test-strategy-zip/v1")

+    @Consumes(MULTIPART_FORM_DATA)

+    @Produces(APPLICATION_JSON)

+    Response deployTestStrategyWithResources(@FormDataParam("bpmn") InputStream bpmn,

+                                             @FormDataParam("resources") InputStream resourcesZip);

+

+    @GET

+    @Path("/testDefinition/v1/processDefinitionKey/{processDefinitionKey}")

+    @Consumes(APPLICATION_JSON)

+    @Produces(APPLICATION_JSON)

+    Response isProcessDefinitionDeployed(@PathParam("processDefinitionKey") String processDefinitionKey);

+

+}

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
index 0000000..8b51c90
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/impl/DeleteProcessInstanceServiceImpl.java
@@ -0,0 +1,96 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service.impl;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.camunda.service.ProcessEngineAwareService;

+import org.oran.otf.camunda.workflow.utility.WorkflowTask;

+import org.oran.otf.common.utility.gson.Convert;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import org.oran.otf.service.DeleteProcessInstanceService;

+import java.util.Date;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import javax.ws.rs.core.Response;

+

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.runtime.ProcessInstance;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.stereotype.Service;

+

+@Service

+public class DeleteProcessInstanceServiceImpl extends ProcessEngineAwareService

+    implements DeleteProcessInstanceService {

+

+  private static Logger logger = LoggerFactory.getLogger(DeleteProcessInstanceServiceImpl.class);

+

+  @Override

+  public Response deleteProcessInstance(String executionId) {

+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();

+

+    Map<String, Object> response =

+        deleteProcessInstanceInternal(

+            executionId, "Deleted via TCU endpoint at " + new Date(System.currentTimeMillis()));

+

+    try {

+      int code = (int) response.get("code");

+      String sRes = Convert.mapToJson(response);

+      if (code == 404) {

+        return ResponseUtility.Build.notFoundWithMessage(sRes);

+      } else if (code == 200) {

+        return ResponseUtility.Build.okRequestWithMessage(sRes);

+      }

+    } catch (ClassCastException cce) {

+      logger.error(cce.getMessage());

+    }

+    // Unhandled response

+    return ResponseUtility.Build.internalServerError();

+  }

+

+  public Map<String, Object> deleteProcessInstanceInternal(

+      String executionId, String deleteReason) {

+    RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();

+

+    ProcessInstance pi =

+        runtimeService.createProcessInstanceQuery().processInstanceId(executionId).singleResult();

+

+    Map<String, Object> response = new HashMap<>();

+

+    if (pi == null) {

+      response.put(

+          "result",

+          String.format("A process instance with the executionId %s was not found.", executionId));

+      response.put("code", 404);

+    } else {

+      List<WorkflowTask> workflowTasks = WorkflowTask.workflowTasksByExecutionId.get(executionId);

+      if (workflowTasks != null) {

+        for (WorkflowTask workflowTask : workflowTasks) {

+          workflowTask.shutdown();

+        }

+      }

+

+      runtimeService.deleteProcessInstance(executionId, deleteReason);

+      response.put("result", "Successfully deleted.");

+      response.put("code", 200);

+    }

+

+    return response;

+  }

+}

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
index 0000000..79eeb45
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/impl/DeleteTestDefinitionServiceImpl.java
@@ -0,0 +1,119 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service.impl;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.common.model.TestDefinition;

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.repository.TestDefinitionRepository;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import org.oran.otf.service.DeleteTestDefinitionService;

+import java.util.List;

+import java.util.Optional;

+import javax.ws.rs.core.Response;

+

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.RepositoryService;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.boot.context.event.ApplicationReadyEvent;

+import org.springframework.context.event.EventListener;

+import org.springframework.stereotype.Service;

+

+@Service

+public class DeleteTestDefinitionServiceImpl implements DeleteTestDefinitionService {

+

+

+    private RepositoryService repositoryService;

+

+    @Autowired

+    private TestDefinitionRepository testDefinitionRepository;

+

+    @EventListener(ApplicationReadyEvent.class)

+    private void initialize(){

+        if(this.repositoryService == null){

+            this.repositoryService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRepositoryService();

+        }

+    }

+

+    // delete a single version by deploymentId

+    @Override

+    public Response deleteTestStrategyByDeploymentId(String deploymentId) {

+        try {

+            repositoryService.deleteDeployment(deploymentId, true);

+            return ResponseUtility.Build.okRequest();

+        } catch (Exception e) {

+            return ResponseUtility.Build.badRequestWithMessage(e.getMessage());

+        }

+    }

+

+

+    // delete all deployment versions given test definition

+    @Override

+    public Response deleteTestStrategyByTestDefinitionId(String testDefinitionId) {

+        Optional<TestDefinition> testDefinitionQuery =

+                testDefinitionRepository.findById(testDefinitionId);

+        if (!testDefinitionQuery.isPresent()) {

+            return Response.status(404).build();

+        }

+        TestDefinition testDefinition = testDefinitionQuery.get();

+

+        List<BpmnInstance> bpmnInstances = testDefinition.getBpmnInstances();

+        // List<Integer> indices = new ArrayList<Integer>();

+        for (int i = 0; i < bpmnInstances.size(); i++) {

+            try {

+                repositoryService.deleteDeployment(bpmnInstances.get(i).getDeploymentId(), true);

+                // indices.add(i);

+            } catch (Exception e) {

+

+            }

+        }

+

+        // for(int i = indices.size()-1; i >=0; i++) {

+        // bpmnInstances.remove(i);

+        // }

+        // testDefinition.setBpmnInstances(new ArrayList<BpmnInstance>());

+        // testDefinitionRepository.save(testDefinition);

+        return ResponseUtility.Build.okRequest();

+

+

+    }

+

+

+    // delete all deployments

+//    public Response deleteAllTestStrategies() {

+////         create a database to retrieve all process definitions

+//        List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()

+//                .orderByProcessDefinitionVersion().asc().list();

+//

+//        final int sizeBefore = processDefinitions.size();

+//

+//        // delete each deployment

+//        processDefinitions.forEach(processDefinition -> {

+//            repositoryService.deleteDeployment(processDefinition.getDeploymentId(), true);

+//        });

+//

+//        final int sizeAfter = processDefinitions.size();

+//

+//        Map<String, Object> response = new HashMap<String, Object>();

+//        if (sizeBefore - sizeAfter == 0)

+//            response.put("numDeletions", sizeBefore);

+//        else

+//            response.put("numDeletions", sizeBefore - sizeAfter);

+//

+//        return ResponseUtility.Build.okRequestWithObject(response);

+//    }

+}

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
index 0000000..7501be7
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/impl/DeveloperServiceImpl.java
@@ -0,0 +1,161 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service.impl;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.camunda.service.CamundaShutdown;

+import org.oran.otf.camunda.service.OtfExternalTaskService;

+import org.oran.otf.camunda.service.OtfWorkflowTaskCleanupService;

+import org.oran.otf.camunda.workflow.utility.WorkflowTask;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import org.oran.otf.service.DeveloperService;

+import com.google.common.base.Strings;

+

+import java.util.Arrays;

+import java.util.Set;

+

+import javax.servlet.http.HttpServletRequest;

+import javax.ws.rs.core.Response;

+

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.context.event.ContextClosedEvent;

+import org.springframework.context.event.EventListener;

+

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;

+import org.camunda.bpm.engine.impl.jobexecutor.JobExecutor;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.stereotype.Service;

+

+@Service

+public class DeveloperServiceImpl implements DeveloperService {

+	

+  private Logger logger = LoggerFactory.getLogger(DeveloperServiceImpl.class);

+  

+  @Autowired

+  CamundaShutdown camundaShutdown;

+  

+  @Value("${otf.camunda.graceful-shutdown.wait-time}")

+  private int gracefulWaitTime;

+

+  private boolean gracefulShutdown = true;

+

+  @Override

+  public Response workflowTaskCleanup(String enabled) {

+    if (Strings.isNullOrEmpty(enabled))

+      return ResponseUtility.Build.badRequestWithMessage(

+          "Path parameter, enabled, cannot be null or empty.");

+

+    OtfWorkflowTaskCleanupService.isEnabled = enabled.equalsIgnoreCase("true");

+    return ResponseUtility.Build.okRequestWithMessage(

+        "Clean up service set to " + OtfWorkflowTaskCleanupService.isEnabled);

+  }

+

+  @Override

+  public Response externalTaskWorker(String enabled) {

+    if (Strings.isNullOrEmpty(enabled)) {

+      return ResponseUtility.Build.badRequestWithMessage(

+          "Path parameter, enabled, cannot be null or empty.");

+    }

+

+    OtfExternalTaskService.isEnabled = enabled.equalsIgnoreCase("true");

+    return ResponseUtility.Build.okRequestWithMessage(

+        "OTF External Task set to " + OtfExternalTaskService.isEnabled);

+  }

+

+  @Override

+  public Response printThreads(HttpServletRequest request) {

+    //Logger logger = LoggerFactory.getLogger(HealthServiceImpl.class);

+    String message = String.format("Health request from %s.", request.getRemoteAddr());

+    logger.info(message);

+

+    WorkflowTask.printWorkflowTaskResources();

+    logger.info("");

+    logger.info("");

+    WorkflowTask.printThreadInformation();

+

+    return ResponseUtility.Build.okRequestWithMessage(message);

+  }

+  

+  @Override

+  public Response activateJobExecutor() {

+	JobExecutor jobExecutor = ((ProcessEngineConfigurationImpl) (BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName)).getProcessEngineConfiguration()).getJobExecutor();

+    if (!jobExecutor.isActive()) {

+		jobExecutor.start();

+    }

+	return ResponseUtility.Build.okRequest();

+  }

+

+  @Override

+  public Response deActivateJobExecutor() {

+	JobExecutor jobExecutor = ((ProcessEngineConfigurationImpl) (BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName)).getProcessEngineConfiguration()).getJobExecutor();

+	if (jobExecutor.isActive()) {

+		jobExecutor.shutdown();

+	}

+	return ResponseUtility.Build.okRequest();

+  }

+  

+  @Override

+  public Response gracefulShutdown() {

+	  return this.gracefulShutdown ? ResponseUtility.Build.okRequestWithMessage(shutdown()) : ResponseUtility.Build.okRequestWithMessage("Graceful shutdown is disabled.");

+  }

+

+    @Override

+    public Response disableGracefulShutdown() {

+        this.gracefulShutdown = false;

+        return ResponseUtility.Build.okRequest();

+    }

+

+    @Override

+    public Response enableGracefulShutdown() {

+        this.gracefulShutdown = true;

+        return ResponseUtility.Build.okRequest();

+    }

+

+    @EventListener(ContextClosedEvent.class)

+  private String shutdown() {

+	  String message = "Graceful shutdown:";

+	  String returnMessage = "Graceful shutdown processes terminated: ";

+	  try {

+	      //disable external task service

+          OtfExternalTaskService.isEnabled = false;

+          //disable job executor

+		  deActivateJobExecutor();

+          logger.info("Disabled job executor and external task service.");

+          logger.info("Starting to sleep...");

+          Thread.sleep(gracefulWaitTime);

+          logger.info("ending to sleep...calling termination service");

+		  // Call Termination service

+		  Set<String> processInterrupted = camundaShutdown.gracefulShutdown();

+          returnMessage = returnMessage + " " + processInterrupted.size();

+		  message += String.format("processesInterrupted %s ",

+		        Arrays.toString(processInterrupted.toArray()));

+

+		  logger.info(message += String.format("processesInterrupted %s ",

+                  Arrays.toString(processInterrupted.toArray())));

+	  } catch (InterruptedException e) {

+		  return "Graceful shutdown processes encountered an error.";

+	  }

+	  return returnMessage;

+  }

+

+

+

+  

+}

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
index 0000000..180f722
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/impl/HealthServiceImpl.java
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service.impl;

+

+import org.oran.otf.common.utility.http.ResponseUtility;

+import org.oran.otf.service.HealthService;

+import javax.ws.rs.core.Response;

+

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.runtime.Incident;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Service;

+

+@Service

+public class HealthServiceImpl implements HealthService {

+

+  private HealthServiceImpl() {

+    // prohibit instantiation

+  }

+

+  @Override

+  public Response getHealth() {

+    return ResponseUtility.Build.okRequestWithMessage("UP");

+  }

+}

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
index 0000000..66e262b
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/impl/ProcessInstanceCompletionServiceImpl.java
@@ -0,0 +1,119 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service.impl;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.common.model.local.OTFProcessInstanceCompletionResponse;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import org.oran.otf.service.ProcessInstanceCompletionService;

+import java.util.List;

+import javax.ws.rs.core.Response;

+

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.HistoryService;

+import org.camunda.bpm.engine.ManagementService;

+import org.camunda.bpm.engine.RuntimeService;

+import org.camunda.bpm.engine.history.*;

+import org.springframework.boot.context.event.ApplicationReadyEvent;

+import org.springframework.context.event.EventListener;

+import org.springframework.stereotype.Service;

+

+@Service

+public class ProcessInstanceCompletionServiceImpl implements ProcessInstanceCompletionService {

+

+    RuntimeService runtimeService;

+

+    ManagementService managementService;

+

+    HistoryService historyService;

+

+

+

+    private ProcessInstanceCompletionServiceImpl() {

+        // prohibit instantiation

+    }

+

+    @EventListener(ApplicationReadyEvent.class)

+    private void initialize(){

+        if(this.runtimeService == null){

+            this.runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService();

+        }

+        if(this.managementService == null){

+            this.managementService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getManagementService();

+        }

+        if(this.historyService == null){

+            this.historyService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getHistoryService();

+        }

+

+    }

+

+    @Override

+    public Response isProcessInstanceComplete(String processInstanceId) {

+

+        HistoricProcessInstance historicProcessInstance = historyService

+                .createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();

+

+        List<HistoricActivityInstance> historicActivityInstance = historyService

+                .createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();

+

+        List<HistoricIncident> historicIncident =

+                historyService.createHistoricIncidentQuery().processInstanceId(processInstanceId).list();

+

+        List<HistoricJobLog> historicJobLog =

+                historyService.createHistoricJobLogQuery().processInstanceId(processInstanceId).list();

+

+        List<HistoricExternalTaskLog> historicExternalTaskLog =

+                historyService.createHistoricExternalTaskLogQuery().processInstanceId(processInstanceId).list();

+

+        List<HistoricVariableInstance> historicVariableInstance =

+                historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).list();

+

+

+

+        OTFProcessInstanceCompletionResponse response = new OTFProcessInstanceCompletionResponse();

+        response.setHistoricProcessInstance(historicProcessInstance);

+        response.setHistoricActivityInstance(historicActivityInstance);

+        response.setHistoricIncident(historicIncident);

+        response.setHistoricJobLog(historicJobLog);

+        response.setHistoricExternalTaskLog(historicExternalTaskLog);

+        response.setHistoricVariableInstance(historicVariableInstance);

+

+

+        return ResponseUtility.Build.okRequestWithObject(response);

+

+        //		Boolean done = runtimeService

+//			.createProcessInstanceQuery()

+//			.processInstanceId(processInstanceId)

+//			.singleResult() == null;

+//

+//		if(done) {

+//			return Response.ok(new ProcessInstanceCompletionResponse("Completed", "Process Instance Completed Execution")).build();

+//		}

+//

+//

+//		Incident incident = runtimeService.createIncidentQuery().processInstanceId(processInstanceId).singleResult();

+//		if(incident != null && incident.getIncidentType().equals("failedJob")) {

+//			String errorMessage = incident.getIncidentMessage();

+//			return Response.ok(new ProcessInstanceCompletionResponse("Failed", errorMessage)).build();

+//		}

+//

+//

+//		else {

+//			return Response.ok(new ProcessInstanceCompletionResponse("In Progress", "Process Instance is active")).build();

+//		}

+    }

+}

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
index 0000000..a607d5c
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/impl/TestControlUnitServiceImpl.java
@@ -0,0 +1,119 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service.impl;

+

+import org.oran.otf.camunda.delegate.otf.common.runnable.AsynchronousTestInstanceCallable;

+import org.oran.otf.camunda.delegate.otf.common.runnable.SynchronousTestInstanceCallable;

+import org.oran.otf.camunda.exception.WorkflowProcessorException;

+import org.oran.otf.camunda.workflow.WorkflowProcessor;

+import org.oran.otf.camunda.workflow.WorkflowRequest;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.model.TestInstance;

+import org.oran.otf.common.repository.TestExecutionRepository;

+import org.oran.otf.common.repository.TestInstanceRepository;

+import org.oran.otf.common.utility.gson.Convert;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import org.oran.otf.service.TestControlUnitService;

+import com.fasterxml.jackson.core.type.TypeReference;

+import java.io.IOException;

+import javax.ws.rs.core.Context;

+import javax.ws.rs.core.HttpHeaders;

+import javax.ws.rs.core.MultivaluedMap;

+import javax.ws.rs.core.Response;

+import org.bson.types.ObjectId;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.stereotype.Service;

+import org.springframework.web.bind.annotation.RequestHeader;

+

+@Service

+public class TestControlUnitServiceImpl implements TestControlUnitService {

+

+  private static Logger logger = LoggerFactory.getLogger(TestControlUnitServiceImpl.class);

+

+  @Autowired

+  TestInstanceRepository testInstanceRepository;

+

+  @Autowired

+  TestExecutionRepository testExecutionRepository;

+

+  @Autowired

+  MongoTemplate mongoOperation;

+

+  @Autowired

+  WorkflowProcessor processor;

+

+  @Override

+  public Response executeByTestInstanceId(String testInstanceId) {

+    try {

+      TestInstance testInstance = testInstanceRepository.findById(testInstanceId).orElse(null);

+      if (testInstance == null) {

+        return Response.status(404).entity("Test Instance not found.").build();

+      }

+

+      WorkflowRequest req = new WorkflowRequest();

+      req.setAsync(false);

+      req.setExecutorId(new ObjectId("5cb72a7e10ba2a0042e6282a"));

+      req.setTestInstanceId(testInstance.get_id());

+      req.setTestData(testInstance.getTestData());

+      req.setVthInput(testInstance.getVthInput());

+      req.setPfloInput(testInstance.getPfloInput());

+      req.setMaxExecutionTimeInMillis(testInstance.getMaxExecutionTimeInMillis());

+      return processWorkflowRequest(req);

+    } catch (Exception e) {

+      return ResponseUtility.Build.internalServerErrorWithMessage(e.getMessage());

+    }

+  }

+

+  @Override

+  public Response executeByWorkflowRequest(String workflowRequestJson) {

+    try {

+      WorkflowRequest workflowRequest =

+          Convert.jsonToObject(workflowRequestJson, new TypeReference<WorkflowRequest>() {

+          });

+

+      return processWorkflowRequest(workflowRequest);

+    } catch (IOException e) {

+      logger.error(e.getMessage());

+      return ResponseUtility.Build.badRequestWithMessage(e.getMessage());

+    }

+  }

+

+  private Response processWorkflowRequest(WorkflowRequest request) {

+    TestExecution testExecution = null;

+    int statusCode = 200;

+    try {

+      if (request.isAsync()) {

+        AsynchronousTestInstanceCallable asynchronousTestInstanceCallable =

+            new AsynchronousTestInstanceCallable(

+                request, testExecutionRepository, processor, mongoOperation);

+        testExecution = asynchronousTestInstanceCallable.call();

+      } else {

+        SynchronousTestInstanceCallable synchronousTestInstanceCallable =

+            new SynchronousTestInstanceCallable(

+                request, testExecutionRepository, processor, mongoOperation);

+        testExecution = synchronousTestInstanceCallable.call();

+      }

+    } catch (WorkflowProcessorException e) {

+      testExecution = e.getWorkflowResponse().getTestExecution();

+      statusCode = e.getWorkflowResponse().getMessageCode();

+    }

+    return ResponseUtility.Build.genericWithMessage(statusCode, testExecution.toString());

+  }

+}

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
index 0000000..e7f932c
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/service/impl/TestDefinitionDeploymentServiceImpl.java
@@ -0,0 +1,134 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.service.impl;

+

+import org.oran.otf.camunda.configuration.OtfCamundaConfiguration;

+import org.oran.otf.common.model.local.OTFDeploymentResponse;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import org.oran.otf.service.TestDefinitionDeploymentService;

+import java.io.InputStream;

+import java.util.List;

+import java.util.zip.ZipInputStream;

+import javax.ws.rs.core.Response;

+

+import org.camunda.bpm.BpmPlatform;

+import org.camunda.bpm.engine.RepositoryService;

+import org.camunda.bpm.engine.impl.persistence.entity.DeploymentEntity;

+import org.camunda.bpm.engine.repository.ProcessDefinition;

+import org.camunda.bpm.model.bpmn.Bpmn;

+import org.camunda.bpm.model.bpmn.BpmnModelInstance;

+import org.camunda.bpm.model.xml.instance.DomElement;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.boot.context.event.ApplicationReadyEvent;

+import org.springframework.context.event.EventListener;

+import org.springframework.stereotype.Service;

+

+@Service

+public class TestDefinitionDeploymentServiceImpl implements TestDefinitionDeploymentService {

+

+    private static Logger logger = LoggerFactory.getLogger(TestDefinitionDeploymentServiceImpl.class);

+

+

+    private RepositoryService repositoryService;

+

+    private TestDefinitionDeploymentServiceImpl() {

+        // prohibit instantiation

+    }

+

+    @EventListener(ApplicationReadyEvent.class)

+    private void initialize(){

+        if(this.repositoryService == null){

+            this.repositoryService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRepositoryService();

+        }

+    }

+

+    public Response deployTestStrategyWithResources(InputStream bpmn, InputStream resourcesZip) {

+

+        if (bpmn == null) {

+            logger.error("no bpmn file provided with name 'bpmn' in multipart form");

+            return ResponseUtility.Build.badRequestWithMessage("No bpmn file provided with name 'bpmn' in multipart form");

+        }

+        DeploymentEntity deployment = null;

+        try {

+            InputStream processDefinitionStream = bpmn;

+            BpmnModelInstance bpmnModelInstance = null;

+            try {

+                bpmnModelInstance = Bpmn.readModelFromStream(processDefinitionStream);

+                Bpmn.validateModel(bpmnModelInstance);

+            }

+            catch(Exception e){

+                e.printStackTrace();

+                return ResponseUtility.Build.badRequestWithMessage("Unable to deploy BPMN: " + e.getMessage());

+            }

+            String namespace = bpmnModelInstance.getDefinitions().getDomElement().getNamespaceURI();

+            List<DomElement> bpmnProcess =

+                    bpmnModelInstance.getDocument().getElementsByNameNs(namespace, "process");

+            if (bpmnProcess.size() != 1) {

+                logger.info("Invalid number of bpmn process tags");

+                return ResponseUtility.Build.internalServerErrorWithMessage("Invalid number of bpmn process tags");

+            } else {

+                String processDefinitionKey = bpmnProcess.get(0).getAttribute("id");

+                if (resourcesZip == null) {

+                    deployment = (DeploymentEntity) repositoryService.createDeployment()

+                            .addModelInstance(processDefinitionKey + ".bpmn", bpmnModelInstance).deploy();

+                } else {

+                    ZipInputStream zis = new ZipInputStream(resourcesZip);

+

+                    deployment = (DeploymentEntity) repositoryService.createDeployment()

+                            .addModelInstance(processDefinitionKey + ".bpmn", bpmnModelInstance)

+                            .addZipInputStream(zis).deploy();

+                }

+            }

+        } catch (Exception e) {

+            logger.info("Error: Creating Deployment: " + e.getMessage());

+            return ResponseUtility.Build.internalServerErrorWithMessage("Error: Creating Deployment: " + e.getMessage());

+        }

+        return Response.ok(generateResponseFromDeployment(deployment)).build();

+    }

+

+    @Override

+    public Response isProcessDefinitionDeployed(String processDefinitionKey) {

+        try {

+            ProcessDefinition definition =

+                    repositoryService

+                            .createProcessDefinitionQuery()

+                            .processDefinitionKey(processDefinitionKey)

+                            .latestVersion()

+                            .singleResult();

+            if (definition != null) {

+                return ResponseUtility.Build.okRequest();

+            }

+            return ResponseUtility.Build.notFound();

+        }

+        catch (Exception e){

+            return ResponseUtility.Build.internalServerErrorWithMessage(e.getMessage());

+        }

+    }

+

+    private OTFDeploymentResponse generateResponseFromDeployment(DeploymentEntity deployment) {

+        if (deployment == null) {

+            return new OTFDeploymentResponse(null, null, null, -1);

+        }

+        String deploymentId = deployment.getId();

+        String id = deployment.getDeployedProcessDefinitions().get(0).getId();

+        String key = deployment.getDeployedProcessDefinitions().get(0).getKey();

+        int version = deployment.getDeployedProcessDefinitions().get(0).getVersion();

+        return new OTFDeploymentResponse(deploymentId, key, id, version);

+    }

+}

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
index 0000000..56b5901
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/spring/configuration/HttpSecurityConfiguration.java
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.spring.configuration;

+

+import org.apache.catalina.Context;

+import org.apache.catalina.connector.Connector;

+import org.apache.tomcat.util.descriptor.web.SecurityCollection;

+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.context.properties.EnableConfigurationProperties;

+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;

+import org.springframework.boot.web.servlet.server.ServletWebServerFactory;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+

+@Configuration

+@EnableConfigurationProperties

+public class HttpSecurityConfiguration {

+    @Value("${security.server.port.http}")

+    private int httpPort;

+

+    @Value("${security.server.port}")

+    private int httpsPort;

+

+    @Value("${security.https-only}")

+    private boolean httpsOnly;

+    @Bean

+    public ServletWebServerFactory servletContainer() {

+        TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {

+            @Override

+            protected void postProcessContext(Context context) {

+                SecurityConstraint securityConstraint = new SecurityConstraint();

+                if(httpsOnly){ securityConstraint.setUserConstraint("CONFIDENTIAL");}

+                SecurityCollection collection = new SecurityCollection();

+                collection.addPattern("/*");

+                securityConstraint.addCollection(collection);

+                context.addConstraint(securityConstraint);

+            }

+        };

+        tomcat.addAdditionalTomcatConnectors(redirectConnector());

+        return tomcat;

+    }

+

+    private Connector redirectConnector() {

+        Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");

+        connector.setScheme("http");

+        connector.setPort(httpPort);

+        connector.setSecure(false);

+        if(httpsOnly) { connector.setRedirectPort(httpsPort); }

+        return connector;

+    }

+}
\ 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
index 0000000..487a8da
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/spring/configuration/JerseyConfiguration.java
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.spring.configuration;

+

+import org.oran.otf.service.impl.DeleteProcessInstanceServiceImpl;

+import org.oran.otf.service.impl.DeleteTestDefinitionServiceImpl;

+import org.oran.otf.service.impl.DeveloperServiceImpl;

+import org.oran.otf.service.impl.HealthServiceImpl;

+import org.oran.otf.service.impl.ProcessInstanceCompletionServiceImpl;

+import org.oran.otf.service.impl.TestControlUnitServiceImpl;

+import org.oran.otf.service.impl.TestDefinitionDeploymentServiceImpl;

+

+import java.util.logging.Logger;

+import org.glassfish.jersey.logging.LoggingFeature;

+import org.glassfish.jersey.media.multipart.MultiPartFeature;

+import org.glassfish.jersey.server.ResourceConfig;

+import org.glassfish.jersey.server.ServerProperties;

+import org.glassfish.jersey.servlet.ServletContainer;

+import org.glassfish.jersey.servlet.ServletProperties;

+import org.springframework.boot.web.servlet.ServletRegistrationBean;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+

+/*

+ * Note: JerseyAutoConfiguration is used to incorporate camunda rest api In this configuration class

+ * we create a new servletregistrationbean to serve at /service/* while camunda serves at /rest/*

+ */

+@Configuration

+public class JerseyConfiguration {

+

+  private static final Logger logger = Logger.getLogger(JerseyConfiguration.class.getName());

+

+  @Bean

+  public ServletRegistrationBean<ServletContainer> applicationJersey() {

+    ServletRegistrationBean<ServletContainer> applicationJersey =

+        new ServletRegistrationBean<>(new ServletContainer(new ApplicationJerseyConfig()));

+    applicationJersey.addUrlMappings("/otf/*");

+    applicationJersey.setName("Open Test Framework - Test Control Unit");

+    applicationJersey.setLoadOnStartup(0);

+    return applicationJersey;

+  }

+

+  public class ApplicationJerseyConfig extends ResourceConfig {

+

+    public ApplicationJerseyConfig() {

+      register(MultiPartFeature.class);

+//      register(

+//          new OTFLoggingFeature(

+//              Logger.getLogger(getClass().getName()),

+//              Level.INFO,

+//              LoggingFeature.Verbosity.PAYLOAD_ANY,

+//              8192));

+

+      logger.info("Registering REST resources.");

+      register(TestControlUnitServiceImpl.class);

+      register(HealthServiceImpl.class);

+      register(DeleteTestDefinitionServiceImpl.class);

+      register(ProcessInstanceCompletionServiceImpl.class);

+      register(TestDefinitionDeploymentServiceImpl.class);

+      register(DeleteProcessInstanceServiceImpl.class);

+      register(DeveloperServiceImpl.class);

+

+      property(ServletProperties.FILTER_FORWARD_ON_404, true);

+      property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);

+      register(new LoggingFeature(logger));

+      logger.info("Finished registering REST resources.");

+    }

+  }

+}

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
index 0000000..36bfb51
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/spring/configuration/OTFLoggingFeature.java
@@ -0,0 +1,242 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.spring.configuration;

+

+import java.io.BufferedInputStream;

+import java.io.ByteArrayOutputStream;

+import java.io.FilterOutputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.io.OutputStream;

+import java.net.URI;

+import java.nio.charset.Charset;

+import java.util.ArrayList;

+import java.util.Base64;

+import java.util.List;

+import java.util.Objects;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import javax.ws.rs.WebApplicationException;

+import javax.ws.rs.client.ClientRequestContext;

+import javax.ws.rs.client.ClientRequestFilter;

+import javax.ws.rs.client.ClientResponseContext;

+import javax.ws.rs.client.ClientResponseFilter;

+import javax.ws.rs.container.ContainerRequestContext;

+import javax.ws.rs.container.ContainerRequestFilter;

+import javax.ws.rs.container.ContainerResponseContext;

+import javax.ws.rs.container.ContainerResponseFilter;

+import javax.ws.rs.core.FeatureContext;

+import javax.ws.rs.core.MultivaluedMap;

+import javax.ws.rs.ext.WriterInterceptor;

+import javax.ws.rs.ext.WriterInterceptorContext;

+import org.glassfish.jersey.logging.LoggingFeature;

+import org.glassfish.jersey.message.MessageUtils;

+

+public class OTFLoggingFeature extends LoggingFeature implements ContainerRequestFilter, ContainerResponseFilter,

+        ClientRequestFilter, ClientResponseFilter, WriterInterceptor {

+

+    private static final boolean printEntity = true;

+    private static final int maxEntitySize = 8 * 1024;

+    private final Logger logger = Logger.getLogger("OTFLoggingFeature");

+    private static final String ENTITY_LOGGER_PROPERTY = OTFLoggingFeature.class.getName();

+    private static final String NOTIFICATION_PREFIX = "* ";

+    private static final String REQUEST_PREFIX = "> ";

+    private static final String RESPONSE_PREFIX = "< ";

+    private static final String AUTHORIZATION = "Authorization";

+    private static final String EQUAL = " = ";

+    private static final String HEADERS_SEPARATOR = ", ";

+    private static List<String> requestHeaders;

+

+    static {

+        requestHeaders = new ArrayList<>();

+        requestHeaders.add(AUTHORIZATION);

+    }

+

+    public OTFLoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer maxEntitySize) {

+        super(logger, level, verbosity, maxEntitySize);

+    }

+

+    @Override

+    public boolean configure(FeatureContext context) {

+        context.register(this);

+        return true;

+    }

+

+    private Object getEmail(Object authorization){

+        try{

+            String encoded = ((String) authorization).split(" ")[1];

+            String decoded =  new String(Base64.getDecoder().decode(encoded));

+            return decoded.split(":")[0];

+        }

+        catch (Exception e){

+            return authorization;

+        }

+    }

+

+    @Override

+    public void filter(final ClientRequestContext context) {

+        final StringBuilder b = new StringBuilder();

+        printHeaders(b, context.getStringHeaders());

+        printRequestLine(b, "Sending client request", context.getMethod(), context.getUri());

+

+        if (printEntity && context.hasEntity()) {

+            final OutputStream stream = new LoggingStream(b, context.getEntityStream());

+            context.setEntityStream(stream);

+            context.setProperty(ENTITY_LOGGER_PROPERTY, stream);

+            // not calling log(b) here - it will be called by the interceptor

+        } else {

+            log(b);

+        }

+    }

+

+    @Override

+    public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {

+        final StringBuilder b = new StringBuilder();

+        printResponseLine(b, "Client response received", responseContext.getStatus());

+

+        if (printEntity && responseContext.hasEntity()) {

+            responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(),

+                    MessageUtils.getCharset(responseContext.getMediaType())));

+        }

+        log(b);

+    }

+

+    @Override

+    public void filter(final ContainerRequestContext context) throws IOException {

+        final StringBuilder b = new StringBuilder();

+        printHeaders(b, context.getHeaders());

+        printRequestLine(b, "Server has received a request", context.getMethod(), context.getUriInfo().getRequestUri());

+

+        if (printEntity && context.hasEntity()) {

+            context.setEntityStream(logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));

+        }

+        log(b);

+    }

+

+    @Override

+    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {

+        final StringBuilder b = new StringBuilder();

+        printResponseLine(b, "Server responded with a response", responseContext.getStatus());

+

+        if (printEntity && responseContext.hasEntity()) {

+            final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());

+            responseContext.setEntityStream(stream);

+            requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);

+            // not calling log(b) here - it will be called by the interceptor

+        } else {

+            log(b);

+        }

+    }

+

+    @Override

+    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {

+        final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);

+        writerInterceptorContext.proceed();

+        if (stream != null) {

+            log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));

+        }

+    }

+

+    private static class LoggingStream extends FilterOutputStream {

+        private final StringBuilder b;

+        private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

+

+        LoggingStream(final StringBuilder b, final OutputStream inner) {

+            super(inner);

+

+            this.b = b;

+        }

+

+        StringBuilder getStringBuilder(Charset charset) {

+            // write entity to the builder

+            final byte[] entity = byteArrayOutputStream.toByteArray();

+

+            b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));

+            if (entity.length > maxEntitySize) {

+                b.append("...more...");

+            }

+            b.append('\n');

+

+            return b;

+        }

+

+        public void write(final int i) throws IOException {

+            if (byteArrayOutputStream.size() <= maxEntitySize) {

+                byteArrayOutputStream.write(i);

+            }

+            out.write(i);

+        }

+    }

+

+    private void printHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {

+        for (String header : requestHeaders) {

+            if (Objects.nonNull(headers.get(header))) {

+                if(header.equalsIgnoreCase("Authorization")){

+                    b.append(header).append(EQUAL).append(getEmail(headers.get(header).get(0))).append(HEADERS_SEPARATOR);

+                }

+                else{

+                    b.append(header).append(EQUAL).append(headers.get(header)).append(HEADERS_SEPARATOR);

+                }

+            }

+        }

+        int lastIndex = b.lastIndexOf(HEADERS_SEPARATOR);

+        if (lastIndex != -1) {

+            b.delete(lastIndex, lastIndex + HEADERS_SEPARATOR.length());

+            b.append("\n");

+        }

+    }

+

+    private void log(final StringBuilder b) {

+        String message = b.toString();

+        if (logger != null) {

+            logger.info(message);

+        }

+    }

+

+    private void printRequestLine(final StringBuilder b, final String note, final String method, final URI uri) {

+        b.append(NOTIFICATION_PREFIX)

+                .append(note)

+                .append(" on thread ").append(Thread.currentThread().getId())

+                .append(REQUEST_PREFIX).append(method).append(" ")

+                .append(uri.toASCIIString()).append("\n");

+    }

+

+    private void printResponseLine(final StringBuilder b, final String note, final int status) {

+        b.append(NOTIFICATION_PREFIX)

+                .append(note)

+                .append(" on thread ").append(Thread.currentThread().getId())

+                .append(RESPONSE_PREFIX)

+                .append(Integer.toString(status))

+                .append("\n");

+    }

+

+    private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {

+        if (!stream.markSupported()) {

+            stream = new BufferedInputStream(stream);

+        }

+        stream.mark(maxEntitySize + 1);

+        final byte[] entity = new byte[maxEntitySize + 1];

+        final int entitySize = stream.read(entity);

+        b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));

+        if (entitySize > maxEntitySize) {

+            b.append("...more...");

+        }

+        b.append('\n');

+        stream.reset();

+        return stream;

+    }

+}
\ 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
index 0000000..665823f
--- /dev/null
+++ b/otf-camunda/src/main/java/org/oran/otf/spring/configuration/OTFMongoConfiguration.java
@@ -0,0 +1,78 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.spring.configuration;

+

+import com.mongodb.MongoClient;

+import com.mongodb.MongoClientOptions;

+import com.mongodb.MongoCredential;

+import com.mongodb.ServerAddress;

+import java.util.ArrayList;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.data.mongodb.config.AbstractMongoConfiguration;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

+

+@Configuration

+@EnableMongoRepositories(basePackages = "org.oran.otf.common.repository")

+public class OTFMongoConfiguration extends AbstractMongoConfiguration {

+  @Value("${otf.mongo.hosts}")

+  private String hosts;

+

+  @Value("${otf.mongo.username}")

+  private String username;

+

+  @Value("${otf.mongo.password}")

+  private String password;

+

+  @Value("${otf.mongo.replica-set}")

+  private String replicaSet;

+

+  @Value("${otf.mongo.database}")

+  private String database;

+

+  @Override

+  protected String getDatabaseName() {

+    return database;

+  }

+

+  @Override

+  public MongoClient mongoClient() {

+    MongoCredential credential =

+        MongoCredential.createScramSha1Credential(username, database, password.toCharArray());

+

+    MongoClientOptions options =

+        MongoClientOptions.builder().sslEnabled(false).requiredReplicaSetName(replicaSet).build();

+

+    String[] hostArray = hosts.split(",");

+    ArrayList<ServerAddress> hosts = new ArrayList<>();

+

+    for (String host : hostArray) {

+      String[] hostSplit = host.split(":");

+      hosts.add(new ServerAddress(hostSplit[0], Integer.parseInt(hostSplit[1])));

+    }

+

+    return new MongoClient(hosts, credential, options);

+  }

+

+  @Override

+  public @Bean

+  MongoTemplate mongoTemplate() {

+    return new MongoTemplate(mongoClient(), database);

+  }

+}

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
index 0000000..77e5a39
--- /dev/null
+++ b/otf-camunda/src/main/resources/META-INF/processes.xml.off
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<process-application

+  xmlns="http://www.camunda.org/schema/1.0/ProcessApplication"

+>

+

+  <process-archive>

+    <properties>

+      <property name="isDeleteUponUndeploy">false</property>

+      <property name="isScanForProcessDefinitions">true</property>

+    </properties>

+  </process-archive>

+

+</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
index 0000000..ef7694c
--- /dev/null
+++ b/otf-camunda/src/main/resources/META-INF/securityFilterRules.json
@@ -0,0 +1,52 @@
+{

+  "pathFilter": {

+    "deniedPaths": [

+      {

+        "path": "/camunda/api/engine/.*",

+        "methods": "*"

+      },

+      {

+        "path": "/camunda/api/cockpit/.*",

+        "methods": "*"

+      },

+      {

+        "path": "/camunda/app/tasklist/{engine}/.*",

+        "methods": "*"

+      },

+      {

+        "path": "/camunda/app/cockpit/{engine}/.*",

+        "methods": "*"

+      }

+    ],

+    "allowedPaths": [

+      {

+        "path": "/camunda/api/engine/engine/",

+        "methods": "GET"

+      },

+      {

+        "path": "/camunda/api/{app:cockpit}/plugin/{engine}/static/.*",

+        "methods": "GET"

+      },

+      {

+        "path": "/camunda/api/{app:cockpit}/plugin/{plugin}/{engine}/.*",

+        "methods": "*",

+        "authorizer": "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer"

+      },

+      {

+        "path": "/camunda/api/engine/engine/{engine}/.*",

+        "methods": "*",

+        "authorizer": "org.camunda.bpm.webapp.impl.security.filter.EngineRequestAuthorizer"

+      },

+      {

+        "path": "/camunda/app/{app:cockpit}/{engine}/.*",

+        "methods": "*",

+        "authorizer": "org.camunda.bpm.webapp.impl.security.filter.ApplicationRequestAuthorizer"

+      },

+      {

+        "path": "/camunda/app/{app:tasklist}/{engine}/.*",

+        "methods": "*",

+        "authorizer": "org.camunda.bpm.webapp.impl.security.filter.ApplicationRequestAuthorizer"

+      }

+    ]

+  }

+}

diff --git a/otf-camunda/src/main/resources/application.yaml b/otf-camunda/src/main/resources/application.yaml
new file mode 100644
index 0000000..bf92302
--- /dev/null
+++ b/otf-camunda/src/main/resources/application.yaml
@@ -0,0 +1,108 @@
+otf:

+  environment: ${ENV}

+  mode: 'debug'

+  camunda:

+    bpm:

+      admin-user:

+        id: 'username'

+        password: 'password'

+        firstName: 'firstname'

+    executor:

+      async.core-pool-size: 50

+      async.max-pool-size: 400

+      async.queue-capacity: 25

+    external-task-client:

+      retry-limit: 0

+      fetch-interval-ms: 1000

+      lock-duration-ms: 43200000

+      max-tasks: 10

+      worker-id: 'otf-camunda-etw'

+    graceful-shutdown:

+      wait-time: 300000

+    executors-active: ${EXECUTORS_ACTIVE}

+    mysql:

+      url: jdbc:mysql://${OTF_CAMUNDA_DB_URL}?useSSL=false&serverTimezone=UTC #&logger=com.mysql.cj.log.Slf4JLogger&profileSQL=true

+      username: ${OTF_CAMUNDA_DB_USERNAME}

+      password: ${OTF_CAMUNDA_DB_PASSWORD}

+  cadi:

+    enabled: true

+    aaf-mech-id: ${AAF_ID}

+    aaf-mech-password: ${AAF_MECH_PASSWORD}

+    aaf-perm-type: ${AAF_PERM_TYPE}

+    hostname: ${CADI_HOSTNAME}

+    keyfile: ${CADI_KEYFILE}

+    aaf-call-timeout: 10000

+    aaf-conn-timeout: 6000

+    aaf-default-realm: 'localhost'

+    aaf-env: 'PROD'

+    aaf-locate-url: 'https://localhost'

+    aaf-lur-class: 'org.onap.aaf.cadi.aaf.v2_0.AAFLurPerm'

+    aaf-url: 'https://localhost'

+    basic-realm: 'localhost'

+    basic-warn: true

+    cadi-latitude: '38.62782'

+    cadi-logLevel: '16384'

+    cadi-longitude: '-90.19458'

+    cadi-protocols: 'TLSv1.1,TLSv1.2'

+

+  mongo:

+    hosts: ${OTF_MONGO_HOSTS}

+    username: ${OTF_MONGO_USERNAME}

+    password: ${OTF_MONGO_PASSWORD}

+    replica-set: ${OTF_MONGO_REPLICASET}

+    database: ${OTF_MONGO_DATABASE}

+  ssl:

+    key-store-type: 'PKCS12'

+    keystore-path: ${OTF_CERT_PATH}

+    keystore-password: ${OTF_CERT_PASS}

+

+#https://stackoverflow.com/questions/50387638/spring-boot-jersey-type-filter-bad-request-400-for-service-consumes-multipar/50423639#50423639

+spring.jersey.filter.order: -100000

+spring.main.allow-bean-definition-overriding: true

+server:

+  port: 8443

+  port.http: 8000

+  tomcat.max-threads: 800

+#  ssl:

+    key-store-type: 'PKCS12'

+    key-store: ${OTF_CERT_PATH}

+    key-store-password: ${OTF_CERT_PASS}

+security:

+  https-only: true

+  require-ssl: false

+  server.port: 8443

+  server.port.http: 8080

+#  server.tomcat.max-threads=800

+#  security.require-ssl=true

+#  server.ssl.key-store-type=PKCS12

+#  server.ssl.key-store=${OTF_CERT_PATH}

+#  server.ssl.key-store-password=${OTF_CERT_PASS}

+

+camunda.bpm.job-execution.enabled: true

+camunda.bpm.job-execution.queueSize: 25

+camunda.bpm.job-execution.corePoolSize: 50

+camunda.bpm.job-execution.maxPoolSize: 400

+#camunda.bpm.job-execution.max-jobs-per-acquisition: 99

+

+camunda.bpm.database.schema-update: true

+logging:

+  level:

+    com.zaxxer.hikari: DEBUG

+

+logging.file.max-history: 5

+logging.file: otf/logs/camunda.log

+logging.path: otf/logs

+

+#logging:

+#  level:

+#    org.camunda.bpm.engine.jobexecutor: OFF

+#    org.camunda.bpm.engine.context: OFF

+#    org.camunda.bpm.extension.reactor.projectreactor: OFF

+    #org.camunda.bpm.extension.reactor.projectreactor.routing.ConsumerFilterRouter: OFF

+    #org.camunda.bpm.extension.reactor.projectreactor: INFO

+    #org.camunda.engine.ProcessEngineException: INFO

+    #org.camunda.bpm.cfg: DEBUG

+    #org.camunda.bpm.engine.impl.persistence.entity.JobEntity: DEBUG

+    #org.camunda.bpm.engine.cmd: DEBUG

+    #org.springframework.web: DEBUG

+    #org.camunda.bpm.engine.rest: DEBUG

diff --git a/otf-camunda/src/main/resources/banner.txt b/otf-camunda/src/main/resources/banner.txt
new file mode 100644
index 0000000..7a62038
--- /dev/null
+++ b/otf-camunda/src/main/resources/banner.txt
@@ -0,0 +1,12 @@
+                                             U  ___ u   _____     _____

+                                            \/"_ \/  |_ " _|   |" ___|

+                                            | | | |    | |    U| |_  u

+                                          .-,_| |_| |   /| |\   \|  _|/

+                                           \_)-\___/   u |_|U    |_|

+                                              \\     _// \\_   )(\\,-

+                                             (__)   (__) (__) (__)(_/

+

+	Open Test Framework: (Blitzcrank)

+	Spring-Boot:  (v2.1.4.RELEASE)

+	Camunda BPM: (v7.10.4-ee)

+	Camunda BPM Spring Boot Starter: (v3.2.0)

diff --git a/otf-camunda/src/main/resources/bpmn/pingGoogleDNS.bpmn b/otf-camunda/src/main/resources/bpmn/pingGoogleDNS.bpmn
new file mode 100644
index 0000000..7ef5852
--- /dev/null
+++ b/otf-camunda/src/main/resources/bpmn/pingGoogleDNS.bpmn
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<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">

+  <bpmn:process id="pingGoogleDns" name="Ping Google DNS" isExecutable="true">

+    <bpmn:startEvent id="StartEvent_1r2e4pd" camunda:asyncBefore="true">

+      <bpmn:outgoing>SequenceFlow_1gpkkbm</bpmn:outgoing>

+    </bpmn:startEvent>

+    <bpmn:endEvent id="EndEvent_0czvyun">

+      <bpmn:incoming>SequenceFlow_1psgifi</bpmn:incoming>

+      <bpmn:terminateEventDefinition id="TerminateEventDefinition_12nqmmc" />

+    </bpmn:endEvent>

+    <bpmn:sequenceFlow id="SequenceFlow_1gpkkbm" sourceRef="StartEvent_1r2e4pd" targetRef="Task_06bcfeo" />

+    <bpmn:sequenceFlow id="SequenceFlow_1psgifi" sourceRef="Task_10nhde5" targetRef="EndEvent_0czvyun" />

+    <bpmn:sequenceFlow id="SequenceFlow_054puyx" sourceRef="Task_1r783jz" targetRef="Task_10nhde5" />

+    <bpmn:sequenceFlow id="SequenceFlow_12x2s0z" sourceRef="Task_06bcfeo" targetRef="Task_1r783jz" />

+    <bpmn:scriptTask id="Task_06bcfeo" name="Set Parameters" scriptFormat="javascript">

+      <bpmn:incoming>SequenceFlow_1gpkkbm</bpmn:incoming>

+      <bpmn:outgoing>SequenceFlow_12x2s0z</bpmn:outgoing>

+      <bpmn:script>var vthInput = {

+	vthInput: {

+		Task_1r783jz: {

+			testData: {

+				targetHost: "8.8.8.8",

+				useJumpServer: false

+			},

+			vthName: "Unused parameter",

+			testConfig: {}

+		}

+	}

+};

+

+execution.setVariable("vthInput", JSON.stringify(vthInput));</bpmn:script>

+    </bpmn:scriptTask>

+    <bpmn:serviceTask id="Task_1r783jz" name="VTH:PING TEST" camunda:delegateExpression="${callTestHeadDelegate}">

+      <bpmn:incoming>SequenceFlow_12x2s0z</bpmn:incoming>

+      <bpmn:outgoing>SequenceFlow_054puyx</bpmn:outgoing>

+    </bpmn:serviceTask>

+    <bpmn:serviceTask id="Task_10nhde5" name="UTIL:LogTestResult" camunda:delegateExpression="${logTestResultDelegate}">

+      <bpmn:incoming>SequenceFlow_054puyx</bpmn:incoming>

+      <bpmn:outgoing>SequenceFlow_1psgifi</bpmn:outgoing>

+    </bpmn:serviceTask>

+  </bpmn:process>

+  <bpmndi:BPMNDiagram id="BPMNDiagram_1">

+    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="pingGoogleDns">

+      <bpmndi:BPMNShape id="StartEvent_1r2e4pd_di" bpmnElement="StartEvent_1r2e4pd">

+        <dc:Bounds x="167" y="117" width="36" height="36" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="416" y="153" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNShape id="EndEvent_0czvyun_di" bpmnElement="EndEvent_0czvyun">

+        <dc:Bounds x="880" y="117" width="36" height="36" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="1117" y="165" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNEdge id="SequenceFlow_1gpkkbm_di" bpmnElement="SequenceFlow_1gpkkbm">

+        <di:waypoint x="203" y="135" />

+        <di:waypoint x="312" y="135" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="441.5" y="114" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNEdge>

+      <bpmndi:BPMNEdge id="SequenceFlow_1psgifi_di" bpmnElement="SequenceFlow_1psgifi">

+        <di:waypoint x="789" y="135" />

+        <di:waypoint x="880" y="135" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="1165" y="133" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNEdge>

+      <bpmndi:BPMNEdge id="SequenceFlow_054puyx_di" bpmnElement="SequenceFlow_054puyx">

+        <di:waypoint x="607" y="135" />

+        <di:waypoint x="689" y="135" />

+      </bpmndi:BPMNEdge>

+      <bpmndi:BPMNEdge id="SequenceFlow_12x2s0z_di" bpmnElement="SequenceFlow_12x2s0z">

+        <di:waypoint x="412" y="135" />

+        <di:waypoint x="507" y="135" />

+      </bpmndi:BPMNEdge>

+      <bpmndi:BPMNShape id="ScriptTask_0anmrwm_di" bpmnElement="Task_06bcfeo">

+        <dc:Bounds x="312" y="95" width="100" height="80" />

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNShape id="ServiceTask_1dnlrl2_di" bpmnElement="Task_1r783jz">

+        <dc:Bounds x="507" y="95" width="100" height="80" />

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNShape id="ServiceTask_04caqpo_di" bpmnElement="Task_10nhde5">

+        <dc:Bounds x="689" y="95" width="100" height="80" />

+      </bpmndi:BPMNShape>

+    </bpmndi:BPMNPlane>

+  </bpmndi:BPMNDiagram>

+</bpmn:definitions>

diff --git a/otf-camunda/src/main/resources/mail-config.properties b/otf-camunda/src/main/resources/mail-config.properties
new file mode 100644
index 0000000..38c3819
--- /dev/null
+++ b/otf-camunda/src/main/resources/mail-config.properties
@@ -0,0 +1,7 @@
+# send mails via SMTP

+mail.transport.protocol=smtp

+

+mail.smtp.host=localhost

+mail.smtp.port=25

+mail.smtp.auth=false

+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
index 0000000..2307939
--- /dev/null
+++ b/otf-camunda/src/test/java/org/oran/otf/api/tests/config/DataConfig.java
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.config;

+

+public class DataConfig {

+

+}

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
index 0000000..06e8464
--- /dev/null
+++ b/otf-camunda/src/test/java/org/oran/otf/api/tests/config/InMemoryConfig.java
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.config;

+

+public class InMemoryConfig {

+

+}

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
index 0000000..edb8b72
--- /dev/null
+++ b/otf-camunda/src/test/java/org/oran/otf/api/tests/shared/MemoryDatabase.java
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.shared;

+

+public class MemoryDatabase {

+

+}

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
index 0000000..ae0e133
--- /dev/null
+++ b/otf-camunda/src/test/java/org/oran/otf/api/tests/unit/common/utility/http/HeadersUtilityTest.java
@@ -0,0 +1,74 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.common.utility.http;

+

+import org.oran.otf.common.utility.http.HeadersUtility;

+import java.util.HashMap;

+import java.util.Map;

+import org.assertj.core.api.Assertions;

+import org.junit.Assert;

+import org.junit.Before;

+import org.junit.BeforeClass;

+import org.junit.Test;

+import org.junit.runner.RunWith;

+import org.mockito.junit.MockitoJUnitRunner;

+

+@RunWith(MockitoJUnitRunner.class)

+public class HeadersUtilityTest {

+  public Map<String, String> headers;

+

+  @Before

+  public void setup(){

+    headers = new HashMap<>();

+    headers.put("GET", "/some/random/route/exmaple");

+    headers.put("Host", "localhost");

+    headers.put("Authorization", "Basic som3R4ndOmStringK3y");

+    headers.put("User-Agent", "James Bond");

+    headers.put("Accept", "*/*");

+

+  }

+  @Test

+  public void authIsMasked(){

+

+    System.out.println("Authorization header in format 'Basic: key' will mask the auth with 4 *");

+    Map<String, String> maskedAuth = HeadersUtility.maskAuth(headers);

+    Assertions.assertThat(maskedAuth.get("Authorization")).isEqualTo("Basic ****");

+  }

+  @Test

+  public void originalHeadersDidNotChange(){

+    System.out.println("Make sure HeaderUtility.maskAuth() does not change the map passed to function");

+    Map<String, String> maskedAuth = HeadersUtility.maskAuth(headers);

+    Assertions.assertThat(headers.get("Authorization")).isEqualTo("Basic som3R4ndOmStringK3y");

+  }

+

+  @Test

+  public void noAuthHeadersPassed(){

+    System.out.println("Make sure HeaderUtility.maskAuth() works if headers are missing a Authorization field");

+    headers.remove("Authorization");

+    Map<String, String> maskedAuth = HeadersUtility.maskAuth(headers);

+    Assertions.assertThat(maskedAuth).isEqualTo(headers);

+  }

+

+  @Test

+  public void authKeyFormatHasNoSpace(){

+    System.out.println("Make sure HeaderUtility.maskAuth() works if Authorization does not follow format 'Authorization: [Type] [Key]'");

+    headers.put("Authorization", "Basicsom3R4ndOmStringK3y");

+    Map<String, String> maskedAuth = HeadersUtility.maskAuth(headers);

+    Assertions.assertThat(maskedAuth.get("Authorization")).isEqualTo("****");

+  }

+

+}

diff --git a/otf-camunda/src/test/resources/application-test.properties b/otf-camunda/src/test/resources/application-test.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/otf-camunda/src/test/resources/application-test.properties
diff --git a/otf-frontend/.dockerignore b/otf-frontend/.dockerignore
new file mode 100644
index 0000000..ae84271
--- /dev/null
+++ b/otf-frontend/.dockerignore
@@ -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
index 0000000..91cebbb
--- /dev/null
+++ b/otf-frontend/.gitignore
@@ -0,0 +1,121 @@
+

+# Created by https://www.gitignore.io/api/node,angular

+

+### Angular ###

+## Angular ##

+# compiled output

+/dist

+/tmp

+/app/**/*.js

+/app/**/*.js.map

+package-lock.json

+

+# dependencies

+/node_modules

+/bower_components

+

+# IDEs and editors

+/.idea

+/.vscode

+

+# misc

+/.sass-cache

+/connect.lock

+/coverage/*

+/libpeerconnection.log

+npm-debug.log

+testem.log

+/typings

+

+# e2e

+/e2e/*.js

+/e2e/*.map

+

+#System Files

+.DS_Store

+

+### Node ###

+# Logs

+logs

+*.log

+npm-debug.log*

+yarn-debug.log*

+yarn-error.log*

+

+# Runtime data

+pids

+*.pid

+*.seed

+*.pid.lock

+

+# Directory for instrumented libs generated by jscoverage/JSCover

+lib-cov

+

+# Coverage directory used by tools like istanbul

+coverage

+

+# nyc test coverage

+.nyc_output

+

+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

+.grunt

+

+# Bower dependency directory (https://bower.io/)

+bower_components

+

+# node-waf configuration

+.lock-wscript

+

+# Compiled binary addons (https://nodejs.org/api/addons.html)

+build/Release

+

+# Dependency directories

+node_modules/

+jspm_packages/

+

+# TypeScript v1 declaration files

+typings/

+

+# Optional npm cache directory

+.npm

+

+# Optional eslint cache

+.eslintcache

+

+# Optional REPL history

+.node_repl_history

+

+# Output of 'npm pack'

+*.tgz

+

+# Yarn Integrity file

+.yarn-integrity

+

+# dotenv environment variables file

+.env

+

+# parcel-bundler cache (https://parceljs.org/)

+.cache

+

+# next.js build output

+.next

+

+# nuxt.js build output

+.nuxt

+

+# vuepress build output

+.vuepress/dist

+

+# Serverless directories

+.serverless

+

+

+# End of https://www.gitignore.io/api/node,angular

+server/config/default.json

+

+# certs

+server/config/cert/*.pem

+server/config/cert/otf.pem

+server/config/cert/privateKey.pem

+# env script

+envScript.sh

diff --git a/otf-frontend/Dockerfile b/otf-frontend/Dockerfile
new file mode 100644
index 0000000..a3fd845
--- /dev/null
+++ b/otf-frontend/Dockerfile
@@ -0,0 +1,31 @@
+FROM node:8.16-alpine

+

+ENV ENV=development

+ENV NAMESPACE=namespace

+ENV APP_NAME=otf-frontend

+ENV APP_VERSION=1.0

+ENV OTF_URL=https://loaclhost:32524/

+ENV OTF_EMAIL=email@email.com

+ENV AUTHENTICATION_SECRET=/ytoYB+iD5HUuDLmeqStcoUPwqw=

+ENV SERVICEAPI_URL=https://localhost:32303/otf/api/

+ENV SERVICEAPI_URIEXECUTETESTINSTANCE=testInstance/execute/v1/id/

+ENV SERVICEAPI_AAFID=username

+ENV SERVICEAPI_AAFPASSWORD=password

+ENV CAMUNDAAPI_URL=https://localhost:31313/

+ENV CAMUNDAAPI_AAFID=username

+ENV CAMUNDAAPI_AAFPASSWORD=password

+ENV MONGO_BASEURL=localhost:27017/

+ENV MONGO_DBOTF=otf

+ENV MONGO_REPLICASET=mongoOTF

+ENV MONGO_USERNAME=username

+ENV MONGO_PASSWORD=password

+

+COPY . /home/node

+WORKDIR /home/node

+

+RUN mkdir -p /otf/logs

+

+RUN npm install --unsafe-perm

+RUN npm run-script build

+

+ENTRYPOINT [ "npm", "start" ]

diff --git a/otf-frontend/Jenkinsfile b/otf-frontend/Jenkinsfile
new file mode 100644
index 0000000..0cc1a01
--- /dev/null
+++ b/otf-frontend/Jenkinsfile
@@ -0,0 +1,153 @@
+#!/usr/bin/env groovy

+

+properties([[$class: 'ParametersDefinitionProperty', parameterDefinitions: [

+[$class: 'hudson.model.StringParameterDefinition', name: 'PHASE', defaultValue: "BUILD"],

+[$class: 'hudson.model.StringParameterDefinition', name: 'ENV', defaultValue: "dev"],

+[$class: 'hudson.model.StringParameterDefinition', name: 'MECHID', defaultValue: "username"],

+[$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"],

+[$class: 'hudson.model.StringParameterDefinition', name: 'OTF_MONGO_DB', defaultValue: "otf_mongo_dev_db"],

+[$class: 'hudson.model.StringParameterDefinition', name: 'OTF_CAMUNDA_DB', defaultValue: "otf_camunda_dev_db"],

+[$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: ""]

+]]])

+

+echo "Build branch: ${env.BRANCH_NAME}"

+

+node("docker"){

+	stage 'Checkout'

+	checkout scm

+	PHASES=PHASE.tokenize( '_' );

+	echo "PHASES : " + PHASES

+

+

+	ARTIFACT_ID="otf-frontend";

+	VERSION="Camille.1.0.3";

+	//TODO: deal with namespace and docker registry

+	NAMESPACE=""

+	DOCKER_REGISTRY=""

+

+	if( ENV.equalsIgnoreCase("dev") ){

+		IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + "/" + ARTIFACT_ID +  ":" + VERSION

+	}

+	if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr")){

+		IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".prod" + "/" + ARTIFACT_ID +  ":" + VERSION

+	}

+	if( ENV.equalsIgnoreCase("st") ){

+		IMAGE_NAME=DOCKER_REGISTRY + "/" + NAMESPACE + ".st" + "/" + ARTIFACT_ID +  ":" + VERSION

+	}

+	echo "Artifact: " + IMAGE_NAME

+

+	withEnv(["PATH=${env.PATH}:${env.WORKSPACE}/linux-amd64", "HELM_HOME=${env.WORKSPACE}"]) {

+

+		echo "PATH=${env.PATH}"

+		echo "HELM_HOME=${env.HELM_HOME}"

+

+		withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {

+

+   			// sh """

+   			// 	docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD

+   			// """

+

+			if (PHASES.contains("BUILD")){

+				stage 'Publish Artifact'

+

+

+					echo "Artifact: " + IMAGE_NAME

+

+					sh """

+						docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD

+						docker build --no-cache -t $IMAGE_NAME .

+						docker push $IMAGE_NAME

+					"""

+

+

+			}

+		}

+

+		if (PHASES.contains("DEPLOY") || PHASES.contains("UNDEPLOY")) {

+

+			stage 'Init Helm'

+

+			//check if helm exists if not install

+			if(fileExists('linux-amd64/helm')){

+				sh """

+					echo "helm is already installed"

+				"""

+			}

+			else{

+			//download helm

+				sh """

+					echo "installing helm"

+					wget  https://storage.googleapis.com/kubernetes-helm/helm-v2.8.2-linux-amd64.tar.gz

+					tar -xf helm-v2.8.2-linux-amd64.tar.gz

+					rm helm-v2.8.2-linux-amd64.tar.gz

+				"""

+			}

+

+			withCredentials([file(credentialsId: KUBE_CONFIG,  variable: 'KUBECONFIG')]) {

+

+				dir('helm'){

+			    	//check if charts are valid, and then perform dry run, if successful then upgrade/install charts

+

+  					if (PHASES.contains("UNDEPLOY") ) {

+						stage 'Undeploy'

+

+  						sh """

+  							helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID

+  						"""

+  					}

+

+			    	//NOTE Double quotes are used below to access groovy variables like artifact_id and tiller_namespace

+    	          	if (PHASES.contains("DEPLOY") ){

+    	          		stage 'Deploy'

+							withCredentials([

+								usernamePassword(credentialsId: OTF_MONGO_DB, usernameVariable: 'USERNAME_MONGO', passwordVariable: 'PASSWORD_MONGO'),

+								usernamePassword(credentialsId: 'FEATHERS_AUTH', usernameVariable: 'USER', passwordVariable: 'AUTHENTICATION_SECRET')

+								]) {

+

+								sh """

+									echo "Validate Yaml"

+									helm lint $ARTIFACT_ID

+

+									echo "View Helm Templates"

+									helm template $ARTIFACT_ID \

+										--set appName=$ARTIFACT_ID \

+										--set version=$VERSION \

+										--set image=$IMAGE_NAME \

+										--set namespace=$TILLER_NAMESPACE \

+									  --set env=$ENV \

+										--set AUTHENTICATION_SECRET=$AUTHENTICATION_SECRET \

+										--set mongo.username=$USERNAME_MONGO \

+										--set mongo.password=$PASSWORD_MONGO

+

+									echo "Perform Dry Run Of Install"

+									helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID \

+										--set appName=$ARTIFACT_ID \

+										--set version=$VERSION \

+										--set image=$IMAGE_NAME \

+										--set namespace=$TILLER_NAMESPACE \

+									  --set env=$ENV \

+										--set AUTHENTICATION_SECRET=$AUTHENTICATION_SECRET \

+										--set mongo.username=$USERNAME_MONGO \

+										--set mongo.password=$PASSWORD_MONGO

+

+									echo "Helm Install/Upgrade"

+					    			helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --timeout 1000 $ARTIFACT_ID $ARTIFACT_ID \

+										--set appName=$ARTIFACT_ID \

+										--set version=$VERSION \

+										--set image=$IMAGE_NAME \

+										--set namespace=$TILLER_NAMESPACE \

+										--set env=$ENV \

+										--set AUTHENTICATION_SECRET=$AUTHENTICATION_SECRET \

+										--set mongo.username=$USERNAME_MONGO \

+										--set mongo.password=$PASSWORD_MONGO

+

+								"""

+							}

+    	          	}

+

+				}

+			}

+      	}

+

+	}

+}

diff --git a/otf-frontend/LICENSES.txt b/otf-frontend/LICENSES.txt
new file mode 100644
index 0000000..695ac56
--- /dev/null
+++ b/otf-frontend/LICENSES.txt
@@ -0,0 +1,28 @@
+Unless otherwise specified, all software contained herein is licensed

+under the Apache License, Version 2.0 (the "Software License");

+you may not use this software except in compliance with the Software

+License. You may obtain a copy of the Software License at

+

+http://www.apache.org/licenses/LICENSE-2.0

+

+Unless required by applicable law or agreed to in writing, software

+distributed under the Software License is distributed on an "AS IS" BASIS,

+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+See the Software License for the specific language governing permissions

+and limitations under the Software License.

+

+

+

+Unless otherwise specified, all documentation contained herein is licensed

+under the Creative Commons License, Attribution 4.0 Intl. (the

+"Documentation License"); you may not use this documentation except in

+compliance with the Documentation License. You may obtain a copy of the

+Documentation License at

+

+https://creativecommons.org/licenses/by/4.0/

+

+Unless required by applicable law or agreed to in writing, documentation

+distributed under the Documentation License is distributed on an "AS IS"

+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+implied. See the Documentation License for the specific language governing

+permissions and limitations under the Documentation License.

diff --git a/otf-frontend/angular.json b/otf-frontend/angular.json
new file mode 100644
index 0000000..53e96b5
--- /dev/null
+++ b/otf-frontend/angular.json
@@ -0,0 +1,147 @@
+{

+  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",

+  "version": 1,

+  "newProjectRoot": "projects",

+  "projects": {

+    "test-ng4": {

+      "root": "",

+      "sourceRoot": "client/src",

+      "projectType": "application",

+      "architect": {

+        "build": {

+          "builder": "@angular-devkit/build-angular:browser",

+          "options": {

+            "outputPath": "client/dist",

+            "index": "client/src/index.html",

+            "main": "client/src/main.ts",

+            "tsConfig": "client/src/tsconfig.app.json",

+            "polyfills": "client/src/polyfills.ts",

+            "assets": [

+              "client/src/assets",

+              "client/src/favicon.ico"

+            ], 

+            "scripts": [

+              "node_modules/jquery/dist/jquery.js",

+              "node_modules/datatables.net/js/jquery.dataTables.js"

+            ],

+            "styles": [

+              "node_modules/font-awesome/css/font-awesome.css",

+              "node_modules/material-design-icons/iconfont/material-icons.css",

+              "client/src/styles/app.scss",

+              "node_modules/bpmn-js/dist/assets/diagram-js.css",

+              "node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css",

+              "node_modules/bpmn-js-properties-panel/styles/properties.less",

+              "node_modules/bpmn-font/dist/css/bpmn.css",

+              "node_modules/diagram-js-minimap/assets/diagram-js-minimap.css",

+              "node_modules/datatables.net-dt/css/jquery.dataTables.css"

+            ]

+          },

+          "configurations": {

+            "production": {

+              "optimization": false,

+              "outputHashing": "all",

+              "sourceMap": false,

+              "extractCss": true,

+              "namedChunks": false,

+              "aot": false,

+              "extractLicenses": true,

+              "vendorChunk": false,

+              "buildOptimizer": false,

+              "fileReplacements": [

+                {

+                  "replace": "client/src/environments/environment.ts",

+                  "with": "client/src/environments/environment.prod.ts"

+                }

+              ]

+            }

+          }

+        },

+        "serve": {

+          "builder": "@angular-devkit/build-angular:dev-server",

+          "options": {

+            "browserTarget": "test-ng4:build"

+          },

+          "configurations": {

+            "production": {

+              "browserTarget": "test-ng4:build:production"

+            }

+          }

+        },

+        "extract-i18n": {

+          "builder": "@angular-devkit/build-angular:extract-i18n",

+          "options": {

+            "browserTarget": "test-ng4:build"

+          }

+        },

+        "test": {

+          "builder": "@angular-devkit/build-angular:karma",

+          "options": {

+            "main": "client/src/test.ts",

+            "karmaConfig": "./karma.conf.js",

+            "polyfills": "client/src/polyfills.ts",

+            "tsConfig": "client/src/tsconfig.spec.json",

+            "scripts": [

+              "node_modules/chart.js/dist/Chart.js"

+            ],

+            "styles": [

+              "node_modules/font-awesome/css/font-awesome.css",

+              "node_modules/material-design-icons/iconfont/material-icons.css",

+              "client/src/styles/app.scss"

+            ],

+            "assets": [

+              "client/src/assets",

+              "client/src/favicon.ico"

+            ]

+          }

+        },

+        "lint": {

+          "builder": "@angular-devkit/build-angular:tslint",

+          "options": {

+            "tsConfig": [

+              "client/src/tsconfig.app.json",

+              "client/src/tsconfig.spec.json"

+            ],

+            "exclude": [

+              "**/node_modules/**"

+            ]

+          }

+        }

+      }

+    },

+    "test-ng4-e2e": {

+      "root": "",

+      "sourceRoot": "",

+      "projectType": "application",

+      "architect": {

+        "e2e": {

+          "builder": "@angular-devkit/build-angular:protractor",

+          "options": {

+            "protractorConfig": "./protractor.conf.js",

+            "devServerTarget": "test-ng4:serve"

+          }

+        },

+        "lint": {

+          "builder": "@angular-devkit/build-angular:tslint",

+          "options": {

+            "tsConfig": [

+              "client/e2e/tsconfig.e2e.json"

+            ],

+            "exclude": [

+              "**/node_modules/**"

+            ]

+          }

+        }

+      }

+    }

+  },

+  "defaultProject": "test-ng4",

+  "schematics": {

+    "@schematics/angular:component": {

+      "prefix": "app",

+      "styleext": "scss"

+    },

+    "@schematics/angular:directive": {

+      "prefix": "app"

+    }

+  }

+}

diff --git a/otf-frontend/client/.gitignore b/otf-frontend/client/.gitignore
new file mode 100644
index 0000000..f94aed4
--- /dev/null
+++ b/otf-frontend/client/.gitignore
@@ -0,0 +1,45 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.

+

+# compiled output

+/tmp

+/out-tsc

+/dist

+/test

+

+# dependencies

+/node_modules

+/test

+package-lock.json

+

+# IDEs and editors

+/.idea

+.project

+.classpath

+.c9/

+*.launch

+.settings/

+*.sublime-workspace

+

+# IDE - VSCode

+.vscode/*

+!.vscode/settings.json

+!.vscode/tasks.json

+!.vscode/launch.json

+!.vscode/extensions.json

+

+# misc

+/.sass-cache

+/connect.lock

+/coverage

+/libpeerconnection.log

+npm-debug.log

+testem.log

+/typings

+

+# e2e

+/e2e/*.js

+/e2e/*.map

+

+# System Files

+.DS_Store

+Thumbs.db

diff --git a/otf-frontend/client/config/.editorconfig b/otf-frontend/client/config/.editorconfig
new file mode 100644
index 0000000..a0e8f5a
--- /dev/null
+++ b/otf-frontend/client/config/.editorconfig
@@ -0,0 +1,13 @@
+# Editor configuration, see http://editorconfig.org

+root = true

+

+[*]

+charset = utf-8

+indent_style = space

+indent_size = 4

+insert_final_newline = true

+trim_trailing_whitespace = true

+

+[*.md]

+max_line_length = off

+trim_trailing_whitespace = false

diff --git a/otf-frontend/client/config/.travis.yml b/otf-frontend/client/config/.travis.yml
new file mode 100644
index 0000000..01e3108
--- /dev/null
+++ b/otf-frontend/client/config/.travis.yml
@@ -0,0 +1,14 @@
+language: node_js

+node_js:

+  - '9'

+  - '10'

+

+install:

+  - npm install

+

+script:

+  - npm run test-ci

+

+cache:

+  directories:

+  - node_modules

diff --git a/otf-frontend/client/config/karma.conf.js b/otf-frontend/client/config/karma.conf.js
new file mode 100644
index 0000000..3be39c4
--- /dev/null
+++ b/otf-frontend/client/config/karma.conf.js
@@ -0,0 +1,50 @@
+// Karma configuration file, see link for more information

+// https://karma-runner.github.io/1.0/config/configuration-file.html

+

+module.exports = function (config) {

+  const defaults = {

+    basePath: '',

+    frameworks: ['jasmine', '@angular-devkit/build-angular'],

+    plugins: [

+      require('karma-jasmine'),

+      require('karma-chrome-launcher'),

+      require('karma-jasmine-html-reporter'),

+      require('karma-coverage-istanbul-reporter'),

+      require('@angular-devkit/build-angular/plugins/karma')

+    ],

+    client:{

+      clearContext: false // leave Jasmine Spec Runner output visible in browser

+    },

+    coverageIstanbulReporter: {

+      dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],

+      fixWebpackSourcePaths: true

+    },

+    angularCli: {

+      environment: 'dev'

+    },

+    reporters: ['progress', 'kjhtml'],

+    port: 9876,

+    colors: true,

+    logLevel: config.LOG_INFO,

+    autoWatch: true,

+    browsers: ['Chrome'],

+    singleRun: false,

+  }

+

+  if (process.env.TEST_CI) {

+    Object.assign(defaults, {

+      autoWatch: false,

+      browsers: ['ChromeHeadlessNoSandbox'],

+      singleRun: true,

+      customLaunchers: {

+        ChromeHeadlessNoSandbox: {

+          base: 'ChromeHeadless',

+          flags: ['--no-sandbox']

+        }

+      },

+      browserNoActivityTimeout: 60000,

+    })

+  }

+

+  config.set(defaults)

+};

diff --git a/otf-frontend/client/config/protractor.conf.js b/otf-frontend/client/config/protractor.conf.js
new file mode 100644
index 0000000..b1a56c1
--- /dev/null
+++ b/otf-frontend/client/config/protractor.conf.js
@@ -0,0 +1,28 @@
+// Protractor configuration file, see link for more information

+// https://github.com/angular/protractor/blob/master/lib/config.ts

+

+const { SpecReporter } = require('jasmine-spec-reporter');

+

+exports.config = {

+  allScriptsTimeout: 11000,

+  specs: [

+    './e2e/**/*.e2e-spec.ts'

+  ],

+  capabilities: {

+    'browserName': 'chrome'

+  },

+  directConnect: true,

+  baseUrl: 'http://localhost:4200/',

+  framework: 'jasmine',

+  jasmineNodeOpts: {

+    showColors: true,

+    defaultTimeoutInterval: 30000,

+    print: function() {}

+  },

+  onPrepare() {

+    require('ts-node').register({

+      project: 'e2e/tsconfig.e2e.json'

+    });

+    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));

+  }

+};

diff --git a/otf-frontend/client/config/tsconfig.json b/otf-frontend/client/config/tsconfig.json
new file mode 100644
index 0000000..bcd2543
--- /dev/null
+++ b/otf-frontend/client/config/tsconfig.json
@@ -0,0 +1,19 @@
+{

+  "compileOnSave": false,

+  "compilerOptions": {

+    "outDir": "./dist/out-tsc",

+    "sourceMap": true,

+    "declaration": false,

+    "moduleResolution": "node",

+    "emitDecoratorMetadata": true,

+    "experimentalDecorators": true,

+    "target": "es5",

+    "typeRoots": [

+      "node_modules/@types"

+    ],

+    "lib": [

+      "es2017",

+      "dom"

+    ]

+  }

+}

diff --git a/otf-frontend/client/config/tslint.json b/otf-frontend/client/config/tslint.json
new file mode 100644
index 0000000..9e1157b
--- /dev/null
+++ b/otf-frontend/client/config/tslint.json
@@ -0,0 +1,139 @@
+{

+  "rulesDirectory": [

+    "../../node_modules/codelyzer"

+  ],

+  "rules": {

+    "arrow-return-shorthand": true,

+    "callable-types": true,

+    "class-name": true,

+    "comment-format": [

+      true,

+      "check-space"

+    ],

+    "curly": true,

+    "eofline": true,

+    "forin": true,

+    "import-blacklist": [

+      true

+    ],

+    "import-spacing": true,

+    "indent": [

+      true,

+      "spaces"

+    ],

+    "interface-over-type-literal": true,

+    "label-position": true,

+    "max-line-length": [

+      true,

+      140

+    ],

+    "member-access": false,

+    "member-ordering": [

+      true,

+      {

+        "order": [

+          "static-field",

+          "instance-field",

+          "static-method",

+          "instance-method"

+        ]

+      }

+    ],

+    "no-arg": true,

+    "no-bitwise": true,

+    "no-console": [

+      true,

+      "debug",

+      "info",

+      "time",

+      "timeEnd",

+      "trace"

+    ],

+    "no-construct": true,

+    "no-debugger": true,

+    "no-duplicate-super": true,

+    "no-empty": false,

+    "no-empty-interface": true,

+    "no-eval": true,

+    "no-inferrable-types": [

+      true,

+      "ignore-params"

+    ],

+    "no-misused-new": true,

+    "no-non-null-assertion": true,

+    "no-shadowed-variable": true,

+    "no-string-literal": false,

+    "no-string-throw": true,

+    "no-switch-case-fall-through": true,

+    "no-trailing-whitespace": true,

+    "no-unnecessary-initializer": true,

+    "no-unused-expression": true,

+    "no-use-before-declare": true,

+    "no-var-keyword": true,

+    "object-literal-sort-keys": false,

+    "one-line": [

+      true,

+      "check-open-brace",

+      "check-catch",

+      "check-else",

+      "check-whitespace"

+    ],

+    "prefer-const": true,

+    "quotemark": [

+      true,

+      "single"

+    ],

+    "radix": true,

+    "semicolon": [

+      true,

+      "always"

+    ],

+    "triple-equals": [

+      true,

+      "allow-null-check"

+    ],

+    "typedef-whitespace": [

+      true,

+      {

+        "call-signature": "nospace",

+        "index-signature": "nospace",

+        "parameter": "nospace",

+        "property-declaration": "nospace",

+        "variable-declaration": "nospace"

+      }

+    ],

+    "typeof-compare": true,

+    "unified-signatures": true,

+    "variable-name": false,

+    "whitespace": [

+      true,

+      "check-branch",

+      "check-decl",

+      "check-operator",

+      "check-separator",

+      "check-type"

+    ],

+    "directive-selector": [

+      true,

+      "attribute",

+      "app",

+      "camelCase"

+    ],

+    "component-selector": [

+      true,

+      "element",

+      "app",

+      "kebab-case"

+    ],

+    "use-input-property-decorator": true,

+    "use-output-property-decorator": true,

+    "use-host-property-decorator": true,

+    "no-input-rename": true,

+    "no-output-rename": true,

+    "use-life-cycle-interface": true,

+    "use-pipe-transform-interface": true,

+    "component-class-suffix": true,

+    "directive-class-suffix": true,

+    "invoke-injectable": true

+  }

+}

diff --git a/otf-frontend/client/e2e/app.e2e-spec.ts b/otf-frontend/client/e2e/app.e2e-spec.ts
new file mode 100644
index 0000000..64bb730
--- /dev/null
+++ b/otf-frontend/client/e2e/app.e2e-spec.ts
@@ -0,0 +1,14 @@
+import { AppPage } from './app.po';

+

+describe('test-ng4 App', () => {

+  let page: AppPage;

+

+  beforeEach(() => {

+    page = new AppPage();

+  });

+

+  it('should display welcome message', () => {

+    page.navigateTo();

+    expect(page.getParagraphText()).toEqual('SB Admin BS4 Angular5');

+  });

+});

diff --git a/otf-frontend/client/e2e/app.po.ts b/otf-frontend/client/e2e/app.po.ts
new file mode 100644
index 0000000..625420f
--- /dev/null
+++ b/otf-frontend/client/e2e/app.po.ts
@@ -0,0 +1,11 @@
+import { browser, by, element } from 'protractor';

+

+export class AppPage {

+  navigateTo() {

+    return browser.get('/');

+  }

+

+  getParagraphText() {

+    return element(by.css('app-root h1')).getText();

+  }

+}

diff --git a/otf-frontend/client/e2e/tsconfig.e2e.json b/otf-frontend/client/e2e/tsconfig.e2e.json
new file mode 100644
index 0000000..a7da750
--- /dev/null
+++ b/otf-frontend/client/e2e/tsconfig.e2e.json
@@ -0,0 +1,14 @@
+{

+  "extends": "../config/tsconfig.json",

+  "compilerOptions": {

+    "outDir": "../out-tsc/e2e",

+    "baseUrl": "./",

+    "module": "commonjs",

+    "target": "es5",

+    "types": [

+      "jasmine",

+      "jasminewd2",

+      "node"

+    ]

+  }

+}

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
index 0000000..23e7bcc
--- /dev/null
+++ b/otf-frontend/client/src/app/access-denied/access-denied-routing.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { AccessDeniedComponent } from './access-denied.component';

+

+const routes: Routes = [

+    {

+        path: '', component: AccessDeniedComponent

+    }

+];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class AccessDeniedRoutingModule {

+}

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
index 0000000..ba588bd
--- /dev/null
+++ b/otf-frontend/client/src/app/access-denied/access-denied.component.html
@@ -0,0 +1,19 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<p>

+  access-denied works!

+</p>

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/access-denied/access-denied.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..249d493
--- /dev/null
+++ b/otf-frontend/client/src/app/access-denied/access-denied.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { AccessDeniedComponent } from './access-denied.component';

+

+describe('AccessDeniedComponent', () => {

+  let component: AccessDeniedComponent;

+  let fixture: ComponentFixture<AccessDeniedComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ AccessDeniedComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(AccessDeniedComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..070c3e9
--- /dev/null
+++ b/otf-frontend/client/src/app/access-denied/access-denied.component.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+

+@Component({

+  selector: 'app-access-denied',

+  templateUrl: './access-denied.component.html',

+  styleUrls: ['./access-denied.component.scss']

+})

+export class AccessDeniedComponent implements OnInit {

+

+  constructor() { }

+

+  ngOnInit() {

+  }

+

+}

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
index 0000000..e52e1ee
--- /dev/null
+++ b/otf-frontend/client/src/app/access-denied/access-denied.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { AccessDeniedModule } from './access-denied.module';

+

+describe('AccessDeniedModule', () => {

+  let accessDeniedModule: AccessDeniedModule;

+

+  beforeEach(() => {

+    accessDeniedModule = new AccessDeniedModule();

+  });

+

+  it('should create an instance', () => {

+    expect(accessDeniedModule).toBeTruthy();

+  });

+});

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
index 0000000..f914e1c
--- /dev/null
+++ b/otf-frontend/client/src/app/access-denied/access-denied.module.ts
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { AccessDeniedRoutingModule } from './access-denied-routing.module';

+import { AccessDeniedComponent } from './access-denied.component';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    AccessDeniedRoutingModule

+  ],

+  declarations: [AccessDeniedComponent]

+})

+export class AccessDeniedModule { }

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
index 0000000..695939d
--- /dev/null
+++ b/otf-frontend/client/src/app/account/account-routing.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { AccountComponent } from './account.component';

+

+

+const routes: Routes = [

+    {

+        path: '', component: AccountComponent

+    }

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class AccountRoutingModule { }

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
index 0000000..b7e38af
--- /dev/null
+++ b/otf-frontend/client/src/app/account/account.component.html
@@ -0,0 +1,25 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<div class="account-page" [@routerTransition]>

+    <div class="row justify-content-md-center">

+        <div class="col-md-4">

+            <img src="assets/images/NetworkLogo.jpg" width="200px" class="user-avatar" />

+            <h1>Open Testing Framework</h1>

+            <h3 id="verifyMessage">{{message}}</h3>

+        </div>

+    </div>

+</div>

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
index 0000000..0db25da
--- /dev/null
+++ b/otf-frontend/client/src/app/account/account.component.scss
@@ -0,0 +1,115 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+$topnav-background-color: #222;

+:host {

+  display: block;

+}

+.account-page {

+  position: absolute;

+  top: 0;

+  left: 0;

+  right: 0;

+  bottom: 0;

+  overflow: auto;

+  background: $topnav-background-color;

+  text-align: center;

+  color: #fff;

+  padding: 3em;

+  .col-lg-4 {

+    padding: 0;

+  }

+  .input-lg {

+    height: 46px;

+    padding: 10px 16px;

+    font-size: 18px;

+    line-height: 1.3333333;

+    border-radius: 0;

+  }

+  .input-underline {

+    background: 0 0;

+    border: none;

+    box-shadow: none;

+    border-bottom: 2px solid rgba(255, 255, 255, 0.5);

+    color: #fff;

+    border-radius: 0;

+  }

+  .input-underline:focus {

+    border-bottom: 2px solid #fff;

+    box-shadow: none;

+  }

+  .rounded-btn {

+    -webkit-border-radius: 50px;

+    border-radius: 50px;

+    color: rgba(255, 255, 255, 0.8);

+    background: $topnav-background-color;

+    border: 2px solid rgba(255, 255, 255, 0.8);

+    font-size: 18px;

+    line-height: 40px;

+    padding: 0 25px;

+  }

+  .rounded-btn:hover,

+  .rounded-btn:focus,

+  .rounded-btn:active,

+  .rounded-btn:visited {

+    color: rgba(255, 255, 255, 1);

+    border: 2px solid rgba(255, 255, 255, 1);

+    outline: none;

+  }

+

+  h1 {

+    font-weight: 300;

+    margin-top: 20px;

+    margin-bottom: 10px;

+    font-size: 36px;

+    small {

+      color: rgba(255, 255, 255, 0.7);

+    }

+  }

+

+  .form-group {

+    padding: 8px 0;

+    input::-webkit-input-placeholder {

+      color: rgba(255, 255, 255, 0.6) !important;

+    }

+

+    input:-moz-placeholder {

+      /* Firefox 18- */

+      color: rgba(255, 255, 255, 0.6) !important;

+    }

+

+    input::-moz-placeholder {

+      /* Firefox 19+ */

+      color: rgba(255, 255, 255, 0.6) !important;

+    }

+

+    input:-ms-input-placeholder {

+      color: rgba(255, 255, 255, 0.6) !important;

+    }

+  }

+  .form-content {

+    padding: 30px 0;

+  }

+  .user-avatar {

+    -webkit-border-radius: 50%;

+    border-radius: 50%;

+    border: 2px solid #fff;

+  }

+

+  #verifyMessage{

+    margin-top: 100px

+  }

+}

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
index 0000000..b8b6b46
--- /dev/null
+++ b/otf-frontend/client/src/app/account/account.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { AccountComponent } from './account.component';

+

+describe('AccountComponent', () => {

+  let component: AccountComponent;

+  let fixture: ComponentFixture<AccountComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ AccountComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(AccountComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..a285598
--- /dev/null
+++ b/otf-frontend/client/src/app/account/account.component.ts
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import {ActivatedRoute} from "@angular/router";

+import {AccountService} from "../shared/services/account.service";

+import { Router} from '@angular/router';

+import { routerTransition } from '../router.animations';

+

+

+@Component({

+  selector: 'app-account',

+  templateUrl: './account.component.html',

+  styleUrls: ['./account.component.scss'],

+  animations: [routerTransition()]

+

+})

+export class AccountComponent implements OnInit {

+    private action: string;

+    private token: string;

+    public message: string;

+  constructor(private router: Router, private route: ActivatedRoute, private accountService: AccountService) { }

+

+  ngOnInit() {

+      this.message = "";

+      this.action = this.route.snapshot.paramMap.get("action");

+      this.route.queryParamMap.subscribe(queryParams => {

+          this.token = queryParams.get("token");

+      });

+      if(this.action && this.token){

+          this.accountService.verify(this.token)

+              .subscribe(

+                  data  => {

+                      this.message = "Thanks for verifying your email. You will be notified when your account is enabled by an admin."

+                  },

+                  error  => {

+                      this.router.navigate(['/dashboard']);

+                  }

+              );

+      }

+      else{

+          this.router.navigate(['/dashboard']);

+      }

+

+  }

+

+}

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
index 0000000..3c1abda
--- /dev/null
+++ b/otf-frontend/client/src/app/account/account.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { AccountModule } from './account.module';

+

+describe('AccountModule', () => {

+  let accountModule: AccountModule;

+

+  beforeEach(() => {

+    accountModule = new AccountModule();

+  });

+

+  it('should create an instance', () => {

+    expect(accountModule).toBeTruthy();

+  });

+});

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
index 0000000..c744693
--- /dev/null
+++ b/otf-frontend/client/src/app/account/account.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { FormsModule } from '@angular/forms';

+import { AccountRoutingModule } from './account-routing.module';

+import { AccountComponent } from './account.component';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    AccountRoutingModule,

+    FormsModule

+  ],

+  declarations: [AccountComponent]

+})

+export class AccountModule { }

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
index 0000000..03f22df
--- /dev/null
+++ b/otf-frontend/client/src/app/app-routing.module.ts
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { AppComponent } from './app.component';

+import { AuthGuard } from './shared';

+

+const routes: Routes = [

+    { path: '', loadChildren: './layout/layout.module#LayoutModule', canActivate: [AuthGuard] },

+    { path: 'login', loadChildren: './login/login.module#LoginModule' },

+    { path: 'signup', loadChildren: './signup/signup.module#SignupModule' },

+    { path: 'error', loadChildren: './server-error/server-error.module#ServerErrorModule' },

+    { path: 'access-denied', loadChildren: './access-denied/access-denied.module#AccessDeniedModule' },

+    { path: 'not-found', loadChildren: './not-found/not-found.module#NotFoundModule' },

+    { path: 'account/:action', loadChildren: './account/account.module#AccountModule' },

+    { path: '**', redirectTo: 'not-found' }

+

+

+];

+

+@NgModule({

+    imports: [RouterModule.forRoot(routes)],

+    exports: [RouterModule]

+})

+export class AppRoutingModule {}

diff --git a/otf-frontend/client/src/app/app.component.html b/otf-frontend/client/src/app/app.component.html
new file mode 100644
index 0000000..9450cf2
--- /dev/null
+++ b/otf-frontend/client/src/app/app.component.html
@@ -0,0 +1,17 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/app.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..4236461
--- /dev/null
+++ b/otf-frontend/client/src/app/app.component.spec.ts
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing'

+import { APP_BASE_HREF } from '@angular/common'

+

+import { AppComponent } from './app.component'

+import { AppModule } from './app.module'

+

+describe('AppComponent', () => {

+  let component: AppComponent

+  let fixture: ComponentFixture<AppComponent>

+

+  beforeEach(

+    async(() => {

+      TestBed.configureTestingModule({

+        imports: [AppModule],

+        providers: [

+          { provide: APP_BASE_HREF, useValue: '/' },

+        ]

+      }).compileComponents()

+    })

+  )

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(AppComponent)

+    component = fixture.componentInstance

+    fixture.detectChanges()

+  })

+

+  it('should create', () => {

+    expect(component).toBeTruthy()

+  })

+})

diff --git a/otf-frontend/client/src/app/app.component.ts b/otf-frontend/client/src/app/app.component.ts
new file mode 100644
index 0000000..bdd0c1d
--- /dev/null
+++ b/otf-frontend/client/src/app/app.component.ts
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { AppGlobals } from './app.global';

+

+

+@Component({

+    selector: 'app-root',

+    templateUrl: './app.component.html',

+    providers: [AppGlobals],

+    styleUrls: ['./app.component.scss']

+})

+export class AppComponent implements OnInit {

+

+    constructor() {

+        

+    }

+

+    ngOnInit() {

+    }

+}

diff --git a/otf-frontend/client/src/app/app.global.ts b/otf-frontend/client/src/app/app.global.ts
new file mode 100644
index 0000000..5fde648
--- /dev/null
+++ b/otf-frontend/client/src/app/app.global.ts
@@ -0,0 +1,23 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from "@angular/core";

+import { HttpHeaders } from "@angular/common/http";

+

+export class AppGlobals {

+    public static baseAPIUrl: string = '/otf/api/v1/';

+    public static version: string = 'Camille.1.0';

+}

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
index 0000000..eadbb3c
--- /dev/null
+++ b/otf-frontend/client/src/app/app.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { AppModule } from './app.module';

+

+describe('AppModule', () => {

+    let appModule: AppModule;

+

+    beforeEach(() => {

+        appModule = new AppModule();

+    });

+

+    it('should create an instance', () => {

+        expect(appModule).toBeTruthy();

+    });

+});

diff --git a/otf-frontend/client/src/app/app.module.ts b/otf-frontend/client/src/app/app.module.ts
new file mode 100644
index 0000000..ff1baba
--- /dev/null
+++ b/otf-frontend/client/src/app/app.module.ts
@@ -0,0 +1,90 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {CommonModule} from '@angular/common';

+import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule} from '@angular/common/http';

+import {NgModule} from '@angular/core';

+import {BrowserModule} from '@angular/platform-browser';

+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

+import {TranslateLoader, TranslateModule} from '@ngx-translate/core';

+import {TranslateHttpLoader} from '@ngx-translate/http-loader';

+import {AppRoutingModule} from './app-routing.module';

+import {AppComponent} from './app.component';

+import {AuthGuard, AdminGuard, SharedPipesModule, PageHeaderModule} from './shared';

+import {FormsModule} from '@angular/forms';

+import {ListService} from './shared/services/list.service';

+import {MatButtonModule, MatDatepickerModule, MatDialogModule, MatIconModule, MatInputModule, MatRadioModule, MatMenu, MatMenuModule} from '@angular/material';

+import {AppGlobals} from './app.global';

+import {ErrorInterceptor} from './error.interceptor';

+import {CookieService} from 'ngx-cookie-service';

+import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker';

+import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io';

+import { FeathersService } from './shared/services/feathers.service';

+import { CoreModule } from './core/core.module';

+import { AbilityModule } from '@casl/angular'

+

+

+const config: SocketIoConfig = { url: '/', options: {transports: ['websocket']} };

+

+// AoT requires an exported function for factories

+export const createTranslateLoader = (http: HttpClient) => {

+    /* for development

+    return new TranslateHttpLoader(

+        http,

+        '/start-angular/SB-Admin-BS4-Angular-6/master/dist/assets/i18n/',

+        '.json'

+    ); */

+    return new TranslateHttpLoader(http, './assets/i18n/', '.json');

+};

+

+@NgModule({

+    imports: [

+        CommonModule,

+        BrowserModule,

+        FormsModule,

+        PageHeaderModule,

+        BrowserAnimationsModule,

+        HttpClientModule,

+        TranslateModule.forRoot({

+            loader: {

+                provide: TranslateLoader,

+                useFactory: createTranslateLoader,

+                deps: [HttpClient]

+            }

+        }),

+        AppRoutingModule,

+        SharedPipesModule,

+        NgxMaterialTimepickerModule.forRoot(),

+        MatButtonModule,

+        MatDialogModule,

+        MatRadioModule,

+        MatInputModule,

+        MatIconModule,

+        MatDatepickerModule,

+        SocketIoModule.forRoot(config),

+        CoreModule,

+        MatMenuModule,

+        AbilityModule.forRoot()

+    ],

+    declarations: [

+        AppComponent,

+    ],

+    providers: [

+        FeathersService, {provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true}, AuthGuard, AdminGuard, ListService, AppGlobals, CookieService],

+    bootstrap: [AppComponent]

+})

+export class AppModule {

+}

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
index 0000000..201e873
--- /dev/null
+++ b/otf-frontend/client/src/app/core/core.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { CoreModule } from './core.module';

+

+describe('CoreModule', () => {

+  let coreModule: CoreModule;

+

+  beforeEach(() => {

+    coreModule = new CoreModule();

+  });

+

+  it('should create an instance', () => {

+    expect(coreModule).toBeTruthy();

+  });

+});

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
index 0000000..d050a07
--- /dev/null
+++ b/otf-frontend/client/src/app/core/core.module.ts
@@ -0,0 +1,56 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { FeathersService } from 'app/shared/services/feathers.service';

+import { ModelService } from 'app/shared/services/model.service';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+import { TestHeadService } from 'app/shared/services/test-head.service';

+import { TestInstanceService } from 'app/shared/services/test-instance.service';

+import { TestExecutionService } from 'app/shared/services/test-execution.service';

+import { AccountService } from 'app/shared/services/account.service';

+import { AuthService } from 'app/shared/services/auth.service';

+import { ExecuteService } from 'app/shared/services/execute.service';

+import { FeedbackService } from 'app/shared/services/feedback.service';

+import { FileTransferService } from 'app/shared/services/file-transfer.service';

+import { FileService } from 'app/shared/services/file.service';

+import { GroupService } from 'app/shared/services/group.service';

+import { SchedulingService } from 'app/shared/services/scheduling.service';

+import { UserService } from 'app/shared/services/user.service';

+

+@NgModule({

+  imports: [

+    CommonModule

+  ],

+  providers: [

+    TestDefinitionService,

+    TestHeadService,

+    TestInstanceService,

+    TestExecutionService,

+    AccountService,

+    AuthService,

+    ExecuteService,

+    FeedbackService,

+    FileTransferService,

+    FileService,

+    GroupService,

+    SchedulingService,

+    UserService

+  ],

+  declarations: []

+})

+export class CoreModule { }

diff --git a/otf-frontend/client/src/app/error.interceptor.ts b/otf-frontend/client/src/app/error.interceptor.ts
new file mode 100644
index 0000000..85f10d0
--- /dev/null
+++ b/otf-frontend/client/src/app/error.interceptor.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';

+import { Observable, throwError } from 'rxjs';

+import { catchError } from 'rxjs/operators';

+import { Router } from '@angular/router';

+import { AuthService } from './shared/services/auth.service';

+

+

+@Injectable()

+export class ErrorInterceptor implements HttpInterceptor {

+    constructor(private auth: AuthService, private router: Router) {}

+

+    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

+        return next.handle(request).pipe(catchError(err => {

+            if (err.status === 401) {

+                // auto logout if 401 response returned from api

+                this.auth.logout();

+                this.router.navigateByUrl('/login');

+            }

+            

+            const error = err.error.message || err.statusText;

+            return throwError(error);

+        }))

+    }

+}
\ 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
index 0000000..81f8614
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/header/header.component.html
@@ -0,0 +1,111 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<nav class="navbar navbar-expand-lg fixed-top">

+        <img src="../../../../assets/images/NetworkLogo.jpg" class="rounded-circle mr-2" style="width:40px; height: 40px;" />

+        <a class="navbar-brand" href="#">Open Test Framework</a>

+        <span *ngIf="groups">

+            <button mat-button style="color:white" #goupMenuTrigger="matMenuTrigger" [matMenuTriggerFor]="groupMenu">{{ selectedGroup?.groupName || 'Select Group' }} <mat-icon>arrow_drop_down</mat-icon></button>

+            <mat-menu #groupMenu="matMenu">

+                <span style="margin-left: 15px; cursor: pointer; color: #007bff" (click)="createGroup()">+ New Group</span>

+                <span *ngFor="let group of groups">

+                    <!-- Handle branch node buttons here -->

+                    <span *ngIf="group.children && group.children.length > 0" style="z-index:1031">

+                        <button mat-menu-item [matMenuTriggerFor]="menu.childMenu" (click)="changeGroup(group)" [disabled]="group.disabled">

+                            {{group.displayName}}

+                        </button>

+                        <app-menu-item #menu [items]="group.children" (dataEvent)="changeGroup($event)"></app-menu-item>

+                    </span>

+                    <!-- Leaf node buttons here -->

+                    <span *ngIf="!group.children || group.children.length === 0" style="z-index:1031">

+                        <button mat-menu-item color="primary" (click)="changeGroup(group)">

+                            {{group.displayName}}

+                        </button>

+                    </span>

+                </span>

+            </mat-menu>

+        </span>

+        <button class="navbar-toggler" type="button" (click)="toggleSidebar()">

+            <!-- <span class="navbar-toggler-icon"></span> -->

+            <i class="fa fa-bars text-muted" aria-hidden="true"></i>

+        </button>

+        <div class="collapse navbar-collapse">

+            <ul class="navbar-nav ml-auto">

+                <!-- <li class="nav-item dropdown" ngbDropdown>

+                    <a href="javascript:void(0)" class="nav-link" ngbDropdownToggle>

+                        <i class="fa fa-language"></i> {{ 'Language' | translate }} <b class="caret"></b>

+                    </a>

+                    <div class="dropdown-menu-right" ngbDropdownMenu>

+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('en')">

+                            {{ 'English' | translate }}

+                        </a>

+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('fr')">

+                            {{ 'French' | translate }}

+                        </a>

+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('ur')">

+                            {{ 'Urdu' | translate }}

+                        </a>

+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('es')">

+                            {{ 'Spanish' | translate }}

+                        </a>

+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('it')">

+                            {{ 'Italian' | translate }}

+                        </a>

+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('fa')">

+                            {{ 'Farsi' | translate }}

+                        </a>

+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('de')">

+                            {{ 'German' | translate }}

+                        </a>

+                        <a class="dropdown-item" href="javascript:void(0)" (click)="changeLang('zh-CHS')">

+                            {{ 'Simplified Chinese' | translate }}

+                        </a>

+                    </div>

+                </li> -->

+                <!--<li *ngIf="groups && selectedGroup">

+                    <span *ngFor="let group of groups">

+                        <!-- Handle branch node buttons here --

+                        <span *ngIf="group.children && group.children.length > 0" style="z-index:1031">

+                            <button mat-button [matMenuTriggerFor]="menu.childMenu" [disabled]="group.disabled">

+                                {{group.displayName}}

+                            </button>

+                            <app-menu-item #menu [items]="group.children"></app-menu-item>

+                        </span>

+                        <!-- Leaf node buttons here --

+                        <span *ngIf="!group.children || group.children.length === 0" style="z-index:1031">

+                            <button mat-button color="primary" (click)="group.click()">

+                                {{group.displayName}}

+                            </button>

+                        </span>

+                    </span>

+                </li> -->

+                <li class="nav-item dropdown" ngbDropdown>

+                    <a href="javascript:void(0)" class="nav-link" ngbDropdownToggle>

+                        <i class="fa fa-user"></i> {{username}} <b class="caret"></b>

+                    </a>

+                    <div class="dropdown-menu-right" ngbDropdownMenu>

+                        <a class="dropdown-item" [routerLink]="['/settings']" >

+                            <i class="fa fa-fw fa-cog"></i> {{ 'Settings' | translate }}

+                        </a>

+                        <a class="dropdown-item" [routerLink]="['/login']" (click)="onLoggedout()">

+                            <i class="fa fa-fw fa-power-off"></i> {{ 'Log Out' | translate }}

+                        </a>

+                    </div>

+                </li>

+            </ul>

+        </div>

+    </nav>

+    

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
index 0000000..591097a
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/header/header.component.scss
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+$topnav-background-color: #045C87;

+

+:host {

+    .navbar {

+       

+        background-color: $topnav-background-color;

+        .navbar-brand {

+            color: #fff;

+            font-size: 1.5em !important;

+        }

+        .nav-item > a {

+            color: #fff;

+            &:hover {

+                color: lighten($topnav-background-color, 50%);

+            }

+        }

+    }

+    .messages {

+        width: 300px;

+        .media {

+            border-bottom: 1px solid #ddd;

+            padding: 5px 10px;

+            &:last-child {

+                border-bottom: none;

+            }

+        }

+        .media-body {

+            h5 {

+                font-size: 13px;

+                font-weight: 600;

+            }

+            .small {

+                margin: 0;

+            }

+            .last {

+                font-size: 12px;

+                margin: 0;

+            }

+        }

+    }

+}

+.my-class{

+    line-height: 25px;

+    height: 25px!important;

+    min-height: unset!important;

+}
\ 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
index 0000000..8d9d5ef
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/header/header.component.spec.ts
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing'

+import { RouterTestingModule } from '@angular/router/testing'

+import { TranslateModule } from '@ngx-translate/core'

+

+import { HeaderComponent } from './header.component'

+import { LayoutModule } from '../../layout.module'

+

+describe('HeaderComponent', () => {

+  let component: HeaderComponent

+  let fixture: ComponentFixture<HeaderComponent>

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      imports: [

+        LayoutModule,

+        RouterTestingModule,

+        TranslateModule.forRoot(),

+      ],

+    })

+    .compileComponents()

+  }))

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(HeaderComponent)

+    component = fixture.componentInstance

+    fixture.detectChanges()

+  })

+

+  it('should create', () => {

+    expect(component).toBeTruthy()

+  })

+})

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
index 0000000..924d50e
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/header/header.component.ts
@@ -0,0 +1,205 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewChild } from '@angular/core';

+import { Router, NavigationEnd } from '@angular/router';

+import { TranslateService } from '@ngx-translate/core';

+import { AuthService } from 'app/shared/services/auth.service';

+import { CookieService } from 'ngx-cookie-service';

+import { GroupService } from 'app/shared/services/group.service';

+import { UserService } from 'app/shared/services/user.service';

+//import { group } from '@angular/animations';

+import { CreateGroupModalComponent } from 'app/shared/modules/create-group-modal/create-group-modal.component';

+import { MatDialog } from '@angular/material/dialog';

+import { NavItem } from 'app/shared/components/menu-item/menu-item.component';

+import { MatMenuTrigger } from '@angular/material';

+

+

+@Component({

+    selector: 'app-header',

+    templateUrl: './header.component.html',

+    styleUrls: ['./header.component.scss']

+})

+

+export class HeaderComponent implements OnInit {

+    pushRightClass: string = 'push-right';

+    myStyle: object = {};

+    myParams: object = {};

+    width: number = 100;

+    height: number = 100;

+

+    public groups: Array<NavItem>;

+    public selectedGroup;

+

+    @ViewChild('goupMenuTrigger') groupMenu: MatMenuTrigger;

+    

+

+    constructor(

+        private translate: TranslateService,

+        public router: Router,

+        private auth: AuthService,

+        private cookie: CookieService,

+        public _groups: GroupService,

+        private user: UserService,

+        private modal: MatDialog

+        

+    ) {

+

+        this.translate.addLangs(['en', 'fr', 'ur', 'es', 'it', 'fa', 'de', 'zh-CHS']);

+        this.translate.setDefaultLang('en');

+        const browserLang = this.translate.getBrowserLang();

+        this.translate.use(browserLang.match(/en|fr|ur|es|it|fa|de|zh-CHS/) ? browserLang : 'en');

+

+

+        this.router.events.subscribe(val => {

+            if (

+                val instanceof NavigationEnd &&

+                window.innerWidth <= 992 &&

+                this.isToggled()

+            ) {

+                this.toggleSidebar();

+            }

+        });

+    }

+    public currentUser;// = {};

+    public username;

+

+

+    ngOnInit() {

+       

+       

+        this.currentUser = JSON.parse(this.cookie.get('currentUser'));

+        this.username = this.currentUser["firstName"] + " " + this.currentUser["lastName"];

+        

+       

+

+        this._groups.setUp();

+

+        this._groups.listChange().subscribe(res => {

+            this.groups = res;

+        });

+       

+        // if (!window.localStorage.getItem("currentGroupId"))

+        // {

+        //     if (!(this.currentUser.defaultGroup)){

+        //         let userPatch = {

+        //             _id : this.currentUser._id,

+        //            defaultGroup : this.currentUser.groups[0].groupId,

+        //            defaultGroupEnabled: false  

+        //         };

+    

+        //         this.user.patch(userPatch).subscribe((res) => {

+        //             console.log(res)

+        //             console.log("Created first default group for user. Default group has been added!")

+        //         })

+            

+        //     }

+        //     else {

+    

+        //         this._groups.setGroup({_id: this.currentUser.defaultGroup})

+    

+        //     }

+        // }

+

+        //this._groups.setUp();

+

+        this._groups.listChange().subscribe(res => {

+            res = this._groups.format(res);

+            //set menu fields

+            this.setNavFields(res);

+            this.groups = res as Array<NavItem>;

+        });

+

+        this._groups.groupChange().subscribe(res => {

+            

+            this.selectedGroup = res;

+        });

+

+    }

+

+    setNavFields(groups){

+        groups.forEach((elem, val) => {

+            groups[val].displayName = elem.groupName;

+            this.setNavFields(groups[val].children);

+        });

+    }

+

+    print(){

+        

+    }

+

+    changeGroup(group) {

+        this.groupMenu.closeMenu();

+       // Patch to add update Default Group

+

+       // If the Default Group Enabled does not exist (users havent saved a default group)

+        if (!this.currentUser.defaultGroupEnabled)

+        {

+            let userPatch = {

+                _id : this.currentUser._id,

+                defaultGroup: group._id

+            };

+

+            this.user.patch(userPatch).subscribe((res) =>{

+                

+                

+            })

+        }

+        // If the default Group Enabled exists (Users saved a default group)

+        else{

+            

+            //Take the default group from the user input

+        }

+

+    

+        

+        this._groups.setGroup(group);

+   

+    }

+

+    createGroup(){

+        this.modal.open(CreateGroupModalComponent, {

+            width: '50%'

+        }).afterClosed().subscribe((result) => {

+            if(result){

+                this.groups.push(result);

+            }

+        });

+    }

+    

+    isToggled(): boolean {

+        const dom: Element = document.querySelector('body');

+        return dom.classList.contains(this.pushRightClass);

+    }

+

+    toggleSidebar() {

+        const dom: any = document.querySelector('body');

+        dom.classList.toggle(this.pushRightClass);

+    }

+

+    rltAndLtr() {

+        const dom: any = document.querySelector('body');

+        dom.classList.toggle('rtl');

+    }

+

+    onLoggedout() {

+        this.auth.logout();

+    }

+

+    changeLang(language: string) {

+        this.translate.use(language);

+    }

+}

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
index 0000000..a664a3c
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.html
@@ -0,0 +1,26 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<nav class="sidebar">

+    <div class="list-group">

+        <a routerLink="/dashboard" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-dashboard"></i>No

+        </a>

+        <a routerLink="/onboarding" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-user"></i>Yes

+        </a>

+    </div>

+</nav>

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
index 0000000..66102b3
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.scss
@@ -0,0 +1,176 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+$topnav-background-color: #000;

+.sidebar {

+    display: none;

+    font-size: 1em;

+    border-radius: 0;

+    position: fixed;

+    z-index: 1000;

+    top: 56px;

+    right: 235px;

+    width: 235px;

+    margin-right: -235px;

+    border: none;

+    border-radius: 0;

+    overflow-y: auto;

+    background-color: $topnav-background-color;

+    bottom: 43px;

+    overflow-x: hidden;

+    padding-bottom: 40px;

+    -webkit-transition: all 0.2s ease-in-out;

+    -moz-transition: all 0.2s ease-in-out;

+    -ms-transition: all 0.2s ease-in-out;

+    -o-transition: all 0.2s ease-in-out;

+    transition: all 0.2s ease-in-out;

+    // border-top: 1px solid rgba(255,255,255,0.3);

+    .list-group {

+        a.list-group-item {

+            background: $topnav-background-color;

+            border: 0;

+            border-radius: 0;

+            color: #999;

+            text-decoration: none;

+            .fa {

+                margin-right: 10px;

+            }

+        }

+        a:hover {

+            background: lighten($topnav-background-color, 10%);

+            color: #fff;

+        }

+        a.router-link-active {

+            background: lighten($topnav-background-color, 10%);

+            color: #fff;

+        }

+        .header-fields {

+            padding-top: 10px;

+        

+            > .list-group-item:first-child {

+                border-top: 1px solid rgba(255, 255, 255, 0.2);

+            }

+        }

+    }

+    .sidebar-dropdown {

+        *:focus {

+            border-radius: none;

+            border: none;

+        }

+        .panel-title {

+            font-size: 1rem;

+            height: 50px;

+            margin-bottom: 0;

+            a {

+                color: #999;

+                text-decoration: none;

+                font-weight: 400;

+                background: $topnav-background-color;

+                span {

+                    position: relative;

+                    display: block;

+                    padding: 0.75rem 1.5rem;

+                    padding-top: 1rem;

+                }

+            }

+            a:hover,

+            a:focus {

+                color: #fff;

+                outline: none;

+                outline-offset: -2px;

+            }

+        }

+        .panel-title:hover {

+            background: lighten($topnav-background-color, 10%);

+        }

+        .panel-collapse {

+            border-radious: 0;

+            border: none;

+            .panel-body {

+                .list-group-item {

+                    border-radius: 0;

+                    background-color: $topnav-background-color;

+                    border: 0 solid transparent;

+                    a {

+                        color: #999;

+                    }

+                    a:hover {

+                        color: #fff;

+                    }

+                }

+                .list-group-item:hover {

+                    background: lighten($topnav-background-color, 10%);

+                }

+            }

+        }

+    }

+}

+.nested-menu {

+    .list-group-item {

+        cursor: pointer;

+    }

+    .nested {

+        list-style-type: none;

+    }

+    ul.submenu {

+        display: none;

+        height: 0;

+    }

+    & .expand {

+        ul.submenu {

+            display: block;

+            list-style-type: none;

+            height: auto;

+            li {

+                a {

+                    color: #fff;

+                    padding: 10px;

+                    display: block;

+                }

+            }

+        }

+    }

+}

+@media screen and (max-width: 992px) {

+    .sidebar {

+        top: 54px;

+        left: 0px;

+    }

+}

+@media print {

+    .sidebar {

+        display: none !important;

+    }

+}

+@media (min-width: 992px) {

+    .header-fields {

+        display: none;

+    }

+}

+

+::-webkit-scrollbar {

+    width: 8px;

+}

+

+::-webkit-scrollbar-track {

+    -webkit-box-shadow: inset 0 0 0px rgba(255, 255, 255, 1);

+    border-radius: 3px;

+}

+

+::-webkit-scrollbar-thumb {

+    border-radius: 3px;

+    -webkit-box-shadow: inset 0 0 3px rgba(255, 255, 255, 1);

+}

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
index 0000000..bce5b0d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { RightSidebarComponent } from './right-sidebar.component';

+

+describe('RightSidebarComponent', () => {

+  let component: RightSidebarComponent;

+  let fixture: ComponentFixture<RightSidebarComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ RightSidebarComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(RightSidebarComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..cf800fa
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/right-sidebar/right-sidebar.component.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+

+@Component({

+  selector: 'app-right-sidebar',

+  templateUrl: './right-sidebar.component.html',

+  styleUrls: ['./right-sidebar.component.scss']

+})

+export class RightSidebarComponent implements OnInit {

+

+  constructor() { }

+

+  ngOnInit() {

+  }

+

+}

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
index 0000000..8f38669
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.html
@@ -0,0 +1,160 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<nav class="sidebar" [ngClass]="{sidebarPushRight: isActive}">

+    <div class="list-group">

+        <a routerLink="/dashboard" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-dashboard"></i>&nbsp;{{ 'Dashboard' | translate }}

+        </a>

+

+        <a [routerLink]="['/test-definitions']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-object-group"></i>&nbsp;{{ 'Test Definitions' | translate }}

+        </a> 

+        <!--<div class="nested-menu">

+            <a class="list-group-item" (click)="addExpandClass('definition')">

+                <span><i class="fa fa-fw fa-object-group"></i>&nbsp; {{ 'Test Definitions' | translate }}</span>

+            </a>

+            <li class="nested" [class.expand]="showMenu === 'definition'">

+                <ul class="submenu">

+                    <li>

+                        <a [routerLink]="['/test-definitions']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-list-ul"></i>&nbsp;{{ 'Collection' | translate }}

+                        </a>

+                    </li>

+                    <li>

+                        <a [routerLink]="['/modeler']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa bpmn-icon-bpmn-io"></i>&nbsp;{{ 'BPMN Modeler' | translate }}

+                        </a>

+                    </li>

+                </ul>

+        </div> -->

+        <a [routerLink]="['/test-instances']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-clone"></i>&nbsp;{{ 'Test Instances' | translate }}

+        </a>

+        <!--<a [routerLink]="['/test-executions']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-bolt"></i>&nbsp;{{ 'Test Executions' | translate }}

+        </a>

+        <a routerLink="/onboarding" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-user"></i>&nbsp;{{ 'Onboarding' | translate }}

+        </a>-->

+        <!--<div class="nested-menu">

+            <a class="list-group-item" (click)="addExpandClass('pages1')">

+                <span><i class="fa fa-fw fa-user"></i>&nbsp;{{ 'Onboarding' | translate }}</span>

+            </a>

+            <li class="nested" [class.expand]="showMenu === 'pages1'">

+                <ul class="submenu">

+                    <li>

+                        <a [routerLink]="['/onboarding/test-head']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-gears"></i>&nbsp;{{ 'Onboard Test Head' | translate }}

+                        </a>

+                    </li>

+                    <li>

+                        <a [routerLink]="['/onboarding/test-definition']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-object-group"></i>&nbsp;{{ 'Onboard Test Definition' | translate }}

+                        </a>

+                    </li>

+                    <li>

+                        <a [routerLink]="['/onboarding/test-instances']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-clone"></i>&nbsp;{{ 'Create Test Instance' | translate }}

+                        </a>

+                    </li>

+                </ul>

+            </li>

+        </div> -->

+        <div class="nested-menu">

+            <a class="list-group-item" (click)="addExpandClass('pages2')">

+                <span><i class="fa fa-folder"></i>&nbsp; {{ 'Resources' | translate }}</span>

+            </a>

+            <li class="nested" [class.expand]="showMenu === 'pages2'">

+                <ul class="submenu">

+                    <li>

+                        <a [routerLink]="['/test-heads']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-gears"></i>&nbsp;{{ 'Virtual Test Heads' | translate }}

+                        </a>

+                    </li>

+                    <!--<li>

+                        <a [routerLink]="['/test-definitions']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-object-group"></i>&nbsp;{{ 'Test Definitions' | translate }}

+                        </a>

+                    </li>

+                    <li>

+                        <a [routerLink]="['/test-instances']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-clone"></i>&nbsp;{{ 'Test Instances' | translate }}

+                        </a>

+                    </li>

+                    <li>

+                        <a [routerLink]="['/test-executions']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-bolt"></i>&nbsp;{{ 'Test Executions' | translate }}

+                        </a>

+                    </li>-->

+                </ul>

+        </div>

+        <!--<a routerLink="/scheduling" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-calendar"></i>&nbsp;{{ 'Scheduling' | translate }}

+        </a>-->

+        <a [routerLink]="['/modeler']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa bpmn-icon-bpmn-io"></i>&nbsp; Test Designer <small style="color: green">beta</small>

+        </a>

+        <a *ngIf="canManageGroup" [routerLink]="['/manage-group']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-group"></i>&nbsp;{{ 'Manage Group' | translate }}

+        </a>

+        <a [routerLink]="['/feedback']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+            <i class="fa fa-fw fa-comment-o"></i>&nbsp;{{ 'Feedback' | translate }}

+        </a>

+     

+

+        <div *ngIf="checkIsAdmin()" class="nested-menu">

+            <a class="list-group-item" (click)="addExpandClass('admin')">

+                <span><i class="fa fa-fw fa-shield"></i>&nbsp; {{ 'Admin' | translate }}</span>

+            </a>

+            <li class="nested" [class.expand]="showMenu === 'admin'">

+                <ul class="submenu">

+                    <li>

+                        <a [routerLink]="['/user-management']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-user"></i>&nbsp;{{ 'User Management' | translate }}

+                        </a>

+                    </li>

+                </ul>

+        </div>

+        <!--<div class="nested-menu">

+            <a class="list-group-item" (click)="addExpandClass('pages3')">

+                <span><i class="fa fa-fw fa-users"></i>&nbsp; {{ 'Manage Group' | translate }}</span>

+            </a>

+            <li class="nested" [class.expand]="showMenu === 'pages3'">

+                <ul class="submenu">

+                    <li>

+                        <a [routerLink]="['/manageGroupUsers']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-user"></i>&nbsp;{{ 'User Management' | translate }}

+                        </a>

+                    </li>

+                    <li>

+                        <a [routerLink]="['/manageGroup']" [routerLinkActive]="['router-link-active']" class="list-group-item">

+                            <i class="fa fa-fw fa-object-group"></i>&nbsp;{{ 'Manage Group' | translate }}

+                        </a>

+                    </li>

+                    

+                </ul>

+        </div> -->

+

+        <a style="position:absolute; bottom: 80px; width: 100%" class="list-group-item" (click)="setHealthStatus()">

+            TCU Engine <small *ngIf="tcuengine" style="color:green">Running</small><small *ngIf="!tcuengine" style="color: red">Down</small>

+        </a>

+        <a style="position:absolute; bottom: 40px; width: 100%" class="list-group-item" (click)="setHealthStatus()">

+            TCU API <small *ngIf="tcuapi" style="color:green">Running</small><small *ngIf="!tcuapi" style="color: red">Down</small>

+        </a>

+        <a style="position:absolute; bottom: 0px; width:100%" class="list-group-item"> {{version}} </a>

+    </div>

+</nav>

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
index 0000000..427e2f4
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.scss
@@ -0,0 +1,175 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+$topnav-background-color: #000;

+.sidebar {

+    font-size: 1em;

+    border-radius: 0;

+    position: fixed;

+    z-index: 1000;

+    top: 56px;

+    left: 235px;

+    width: 235px;

+    margin-left: -235px;

+    border: none;

+    border-radius: 0;

+    overflow-y: auto;

+    background-color: $topnav-background-color;

+    bottom: 0;

+    overflow-x: hidden;

+    padding-bottom: 40px;

+    -webkit-transition: all 0.2s ease-in-out;

+    -moz-transition: all 0.2s ease-in-out;

+    -ms-transition: all 0.2s ease-in-out;

+    -o-transition: all 0.2s ease-in-out;

+    transition: all 0.2s ease-in-out;

+    // border-top: 1px solid rgba(255,255,255,0.3);

+    .list-group {

+        a.list-group-item {

+            background: $topnav-background-color;

+            border: 0;

+            border-radius: 0;

+            color: #999;

+            text-decoration: none;

+            .fa {

+                margin-right: 10px;

+            }

+        }

+        a:hover {

+            background: lighten($topnav-background-color, 10%);

+            color: #fff;

+        }

+        a.router-link-active {

+            background: lighten($topnav-background-color, 10%);

+            color: #fff;

+        }

+        .header-fields {

+            padding-top: 10px;

+        

+            > .list-group-item:first-child {

+                border-top: 1px solid rgba(255, 255, 255, 0.2);

+            }

+        }

+    }

+    .sidebar-dropdown {

+        *:focus {

+            border-radius: none;

+            border: none;

+        }

+        .panel-title {

+            font-size: 1rem;

+            height: 50px;

+            margin-bottom: 0;

+            a {

+                color: #999;

+                text-decoration: none;

+                font-weight: 400;

+                background: $topnav-background-color;

+                span {

+                    position: relative;

+                    display: block;

+                    padding: 0.75rem 1.5rem;

+                    padding-top: 1rem;

+                }

+            }

+            a:hover,

+            a:focus {

+                color: #fff;

+                outline: none;

+                outline-offset: -2px;

+            }

+        }

+        .panel-title:hover {

+            background: lighten($topnav-background-color, 10%);

+        }

+        .panel-collapse {

+            border-radious: 0;

+            border: none;

+            .panel-body {

+                .list-group-item {

+                    border-radius: 0;

+                    background-color: $topnav-background-color;

+                    border: 0 solid transparent;

+                    a {

+                        color: #999;

+                    }

+                    a:hover {

+                        color: #fff;

+                    }

+                }

+                .list-group-item:hover {

+                    background: lighten($topnav-background-color, 10%);

+                }

+            }

+        }

+    }

+}

+.nested-menu {

+    .list-group-item {

+        cursor: pointer;

+    }

+    .nested {

+        list-style-type: none;

+    }

+    ul.submenu {

+        display: none;

+        height: 0;

+    }

+    & .expand {

+        ul.submenu {

+            display: block;

+            list-style-type: none;

+            height: auto;

+            li {

+                a {

+                    color: #fff;

+                    padding: 10px;

+                    display: block;

+                }

+            }

+        }

+    }

+}

+@media screen and (max-width: 992px) {

+    .sidebar {

+        top: 54px;

+        left: 0px;

+    }

+}

+@media print {

+    .sidebar {

+        display: none !important;

+    }

+}

+@media (min-width: 992px) {

+    .header-fields {

+        display: none;

+    }

+}

+

+::-webkit-scrollbar {

+    width: 8px;

+}

+

+::-webkit-scrollbar-track {

+    -webkit-box-shadow: inset 0 0 0px rgba(255, 255, 255, 1);

+    border-radius: 3px;

+}

+

+::-webkit-scrollbar-thumb {

+    border-radius: 3px;

+    -webkit-box-shadow: inset 0 0 3px rgba(255, 255, 255, 1);

+}

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
index 0000000..994bb5e
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.spec.ts
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing'

+import { RouterTestingModule } from '@angular/router/testing'

+import { TranslateModule } from '@ngx-translate/core'

+

+import { SidebarComponent } from './sidebar.component'

+import { LayoutModule } from '../../layout.module'

+

+describe('SidebarComponent', () => {

+  let component: SidebarComponent

+  let fixture: ComponentFixture<SidebarComponent>

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      imports: [

+        LayoutModule,

+        RouterTestingModule,

+        TranslateModule.forRoot(),

+      ],

+    })

+    .compileComponents()

+  }))

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(SidebarComponent)

+    component = fixture.componentInstance

+    fixture.detectChanges()

+  })

+

+  it('should create', () => {

+    expect(component).toBeTruthy()

+  })

+})

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
index 0000000..8034055
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/sidebar/sidebar.component.ts
@@ -0,0 +1,147 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { Router, NavigationEnd } from '@angular/router';

+import { TranslateService } from '@ngx-translate/core';

+import { AppGlobals } from 'app/app.global';

+import {CookieService} from "ngx-cookie-service";

+import { HealthService } from 'app/shared/services/health.service';

+import { UserService } from 'app/shared/services/user.service';

+import { GroupService } from 'app/shared/services/group.service';

+import { Group, Groups } from 'app/shared/models/group.model';

+

+@Component({

+    selector: 'app-sidebar',

+    templateUrl: './sidebar.component.html',

+    styleUrls: ['./sidebar.component.scss']

+})

+export class SidebarComponent implements OnInit {

+    isActive: boolean = false;

+    showMenu: string = '';

+    pushRightClass: string = 'push-right';

+    version = AppGlobals.version

+    tcuapi: boolean;

+    tcuengine: boolean;

+    isAdmin: boolean = false;

+

+    canManageGroup = false;

+

+    currentGroupId;

+

+    constructor(private translate: TranslateService, public router: Router, public user: UserService, private health: HealthService, private cookie: CookieService, public group: GroupService) {

+        this.translate.addLangs(['en', 'fr', 'ur', 'es', 'it', 'fa', 'de']);

+        this.translate.setDefaultLang('en');

+        const browserLang = this.translate.getBrowserLang();

+        this.translate.use(browserLang.match(/en|fr|ur|es|it|fa|de/) ? browserLang : 'en');

+        this.checkIsAdmin();

+        this.router.events.subscribe(val => {

+            if (

+                val instanceof NavigationEnd &&

+                window.innerWidth <= 992 &&

+                this.isToggled()

+            ) {

+                this.toggleSidebar();

+            }

+        });

+    }

+

+    ngOnInit(){

+        if(this.group.getGroup()){

+            this.checkManage(this.group.getGroup());

+        }

+        this.group.groupChange().subscribe(group => {

+            this.checkManage(group);

+        })

+        this.setHealthStatus();

+    }

+

+    checkManage(group){

+        this.canManageGroup = this.user.ability.can('management', new Groups(group));

+    }

+

+    setHealthStatus(){

+        this.health.get('tcu-api').subscribe(res => {

+            if(res['code'] == 200 || res['statusCode'] == 200){

+                this.tcuapi = true;

+            }else{

+                this.tcuapi = false;

+            }

+        }, err => {

+            this.tcuapi = false;

+        });

+

+        this.health.get('tcu-engine').subscribe(res => {

+            

+            if(res['code'] == 200 || res['statusCode'] == 200){

+                this.tcuengine = true;

+            }else{

+                this.tcuengine = false;

+            }

+        }, err => {

+            

+            this.tcuengine = false;

+        });

+    }

+

+    eventCalled() {

+        this.isActive = !this.isActive;

+    }

+

+    addExpandClass(element: any) {

+        if (element === this.showMenu) {

+            this.showMenu = '0';

+        } else {

+            this.showMenu = element;

+        }

+    }

+

+    isToggled(): boolean {

+        const dom: Element = document.querySelector('body');

+        return dom.classList.contains(this.pushRightClass);

+    }

+

+    toggleSidebar() {

+        const dom: any = document.querySelector('body');

+        dom.classList.toggle(this.pushRightClass);

+    }

+

+    rltAndLtr() {

+        const dom: any = document.querySelector('body');

+        dom.classList.toggle('rtl');

+    }

+

+    changeLang(language: string) {

+        this.translate.use(language);

+    }

+

+    onLoggedout() {

+        localStorage.removeItem('isLoggedin');

+    }

+

+    checkIsAdmin() {

+        if (this.cookie.get('access_token') && this.cookie.get('currentUser')) {

+            let currentUser = JSON.parse(this.cookie.get('currentUser'));

+            if (currentUser['permissions'].indexOf('admin') >= 0) {

+                this.isAdmin = true;

+                return true;

+            }

+        }

+        this.isAdmin = false;

+        return false;

+    }

+

+}

diff --git "a/otf-frontend/client/src/app/layout/components/stats/assets/icons/ProfessionalHeadshot\0502\051.png" "b/otf-frontend/client/src/app/layout/components/stats/assets/icons/ProfessionalHeadshot\0502\051.png"
new file mode 100644
index 0000000..6fc07ef
--- /dev/null
+++ "b/otf-frontend/client/src/app/layout/components/stats/assets/icons/ProfessionalHeadshot\0502\051.png"
Binary files 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
index 0000000..2fad5ae
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/assets/icons/download.jfif
Binary files 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
index 0000000..22ccbe3
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/assets/icons/equalizer.gif
Binary files 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
index 0000000..5d979f7
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.gif
Binary files 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
index 0000000..d34f7b3
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.svg
@@ -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
index 0000000..d895b76
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.pug
@@ -0,0 +1,139 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div(style="position: relative;", ng-model)

+

+  .row()

+

+    h4(mat-dialog-title, style="padding-left: 15px;") Statistics Filters

+

+    button(mat-icon-button, (click)="close()", style="position: absolute; right: 0px;")

+      mat-icon close

+

+  div(mat-dialog-content)

+

+    //- mat-expansion-panel(style="margin-top: 5px;")

+    //-   mat-expansion-panel-header 

+    //-     mat-panel-title(style="font-weight: bold") Overall

+    //-     //- mat-panel-description Filters for all charts. 

+    //-   .row

+    //-     .col-3

+    //-       mat-form-field

+    //-         input(matInput, [matDatepicker]="allStartPicker", [(ngModel)]="allFilters.startDate", [min]="minDate", [max]="maxDate", placeholder="Start Date")

+    //-         mat-datepicker-toggle(matSuffix [for]="allStartPicker")

+    //-         mat-datepicker(#allStartPicker)

+

+    //-       mat-form-field

+    //-         input(matInput, [matDatepicker]="allEndPicker", [(ngModel)]="allFilters.endDate", [min]="minDate", [max]="maxDate", placeholder="End Date")

+    //-         mat-datepicker-toggle(matSuffix [for]="allEndPicker")

+    //-         mat-datepicker(#allEndPicker)

+

+    mat-expansion-panel

+      mat-expansion-panel-header 

+        mat-panel-title(style="font-weight: bold") Test Definitions

+        //- mat-panel-description Filters for test definition charts.

+      .row

+        .col-6

+

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              mat-label Test Definitions

+              mat-select( [(value)]="tdFilters.selected", multiple)

+                mat-option(*ngFor="let testDefinition of testDefinitions", [value]="testDefinition") {{testDefinition.viewValue}}

+

+        .col-6

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              input(matInput, [matDatepicker]="TDStartPicker", [(ngModel)]="tdFilters.startDate", [min]="minDate", [max]="maxDate", placeholder="Start Date")

+              mat-datepicker-toggle(matSuffix [for]="TDStartPicker")

+              mat-datepicker(#TDStartPicker)

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              input(matInput, [matDatepicker]="TDEndPicker", [(ngModel)]="tdFilters.endDate", [min]="minDate", [max]="maxDate", placeholder="End Date")

+              mat-datepicker-toggle(matSuffix [for]="TDEndPicker")

+              mat-datepicker(#TDEndPicker) 

+

+    mat-expansion-panel

+      mat-expansion-panel-header 

+        mat-panel-title(style="font-weight: bold") Test Instances

+        //- mat-panel-description Filters for test instance charts.

+      .row

+

+        .col-6

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              mat-label Test Definitions

+              mat-select([(value)]="tiFilters.selectedTDs", multiple)

+                mat-option(*ngFor="let testDefinition of testDefinitions", [value]="testDefinition.id") {{testDefinition.viewValue}}

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              mat-label Test Instances

+              mat-select([(value)]="tiFilters.selectedTIs", multiple)

+                mat-option(*ngFor="let testInstance of testInstances", [value]="testInstance.id") {{testInstance.viewValue}}

+

+        .col-6

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              input(matInput, [matDatepicker]="TIStartPicker", [(ngModel)]="tiFilters.startDate", [min]="minDate", [max]="maxDate", placeholder="Start Date")

+              mat-datepicker-toggle(matSuffix [for]="TIStartPicker")

+              mat-datepicker(#TIStartPicker)

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              input(matInput, [matDatepicker]="TIEndPicker", [(ngModel)]="tiFilters.endDate", [min]="minDate", [max]="maxDate", placeholder="End Date")

+              mat-datepicker-toggle(matSuffix [for]="TIEndPicker")

+              mat-datepicker(#TIEndPicker) 

+

+    mat-expansion-panel(style="margin-bottom: 5px;")

+      mat-expansion-panel-header 

+        mat-panel-title(style="font-weight: bold") Scheduled Tests

+        //- mat-panel-description Filters for test schedule table.

+

+      .row   

+

+        .col-6

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              mat-label Test Instances

+              mat-select([(value)]="scheduleFilters.selectedInstances", multiple)

+                mat-option(*ngFor="let instance of testInstances", [value]="instance.id") {{instance.viewValue}}

+

+        .col-6

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              input(matInput, [matDatepicker]="scheduleStartPicker", [(ngModel)]="scheduleFilters.startDate", placeholder="Start Date")

+              mat-datepicker-toggle(matSuffix [for]="scheduleStartPicker")

+              mat-datepicker(#scheduleStartPicker)

+          .row

+            mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+              input(matInput, [matDatepicker]="scheduleEndPicker", [(ngModel)]="scheduleFilters.endDate", placeholder="End Date")

+              mat-datepicker-toggle(matSuffix [for]="scheduleEndPicker")

+            mat-datepicker(#scheduleEndPicker)

+

+        //- .col-3

+        //-   .row

+        //-     mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+        //-       input(matInput, [matDatepicker]="scheduleStartTimePicker", [(ngModel)]="scheduleFilters.timeRangeStart", [min]="minDate", [max]="maxDate", placeholder="Time Range Start")

+        //-       mat-datepicker-toggle(matSuffix [for]="scheduleStartTimePicker")

+        //-       mat-datepicker(#scheduleStartTimePicker)

+        //-   .row

+        //-     mat-form-field(style="margin-left: auto; margin-right: auto; width: 90%")

+        //-       input(matInput, [matDatepicker]="scheduleEndTimePicker", [(ngModel)]="scheduleFilters.timeRangeEnd", [min]="minDate", [max]="maxDate", placeholder="Time Range End")

+        //-       mat-datepicker-toggle(matSuffix [for]="scheduleEndTimePicker")

+        //-     mat-datepicker(#scheduleEndTimePicker)

+

+    .row(style="padding: 10px;")

+      //- button(mat-raised-button, style="margin-left: auto; margin-right: 5px;") Clear All

+      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
index 0000000..20ad5c4
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.scss
@@ -0,0 +1,23 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+*{

+    //border: 1px solid black;

+}

+

+hr{

+    padding: 0px;

+}

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
index 0000000..c04bb71
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { FilterModalComponent } from './filter-modal.component';

+

+describe('FilterModalComponent', () => {

+  let component: FilterModalComponent;

+  let fixture: ComponentFixture<FilterModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ FilterModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(FilterModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..c1ab2c7
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/filter-modal/filter-modal.component.ts
@@ -0,0 +1,151 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

+import { FormControl } from '@angular/forms';

+import * as moment from 'moment';

+

+import { StatsService } from '../stats.service';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+import { TestInstanceService } from 'app/shared/services/test-instance.service';

+import { GroupService } from 'app/shared/services/group.service';

+

+@Component({

+  selector: 'app-filter-modal',

+  templateUrl: './filter-modal.component.pug',

+  styleUrls: ['./filter-modal.component.scss']

+})

+

+export class FilterModalComponent implements OnInit {

+

+  public group;

+  public allFilters = {

+    startDate: "",

+    endDate: ""

+  };

+  public tdFilters = {

+    selected: [],

+    startDate: "",

+    endDate: "",

+  };

+  public tiFilters = {

+    selectedTDs: [],

+    selectedTIs: [],

+    startDate: "",

+    endDate: "",

+    multiLineLimit: 5

+  };

+  public scheduleFilters = {

+    startDate: "",

+    endDate: "",

+    timeRangeStart: "",

+    timeRangeEnd: "",

+    selectedInstances: [],

+  }

+  // public vthFilters = {

+  //   selected: [],

+  //   startDate: "",

+  //   endDate: "",

+  // };

+

+  public minDate;

+  public maxDate;

+

+  public testDefinitions: Array<any> = [];

+  public testInstances: Array<any> = [];

+  //public scheduleInstances: Array<any> = [];

+  //public vths = [];

+

+  constructor(

+    public dialogRef: MatDialogRef<FilterModalComponent>,

+    public statsService: StatsService,

+    public tdService: TestDefinitionService,

+    public groupService: GroupService,

+    public tiService: TestInstanceService

+  ) {

+    this.minDate = new Date(moment().subtract(1, 'year').format('L'));

+    this.maxDate = new Date(moment().format('L'));

+

+  }

+

+  ngOnInit() {

+    //populate the td, ti, and vth arrays up there. or import them?

+    this.setTDList();

+    this.setTIList();

+  }

+

+  setTDList() {

+    this.tdService.find({

+      groupId: this.groupService.getGroup()["_id"],

+      $select: ['testName', 'testDescription', "_id"],

+      $limit: -1,

+      $sort:{

+        testName:1

+      }

+    }).subscribe(result => {

+      for (let index in result) {

+        this.testDefinitions.push({id: result[index]._id, viewValue: result[index].testName });

+      }

+    })

+  }

+

+  setTIList() {

+    this.tiService.find({

+      groupId: this.groupService.getGroup()["_id"],

+      $select: ['testInstanceName', 'testInstanceDescription', "_id"],

+      $limit: -1,

+      $sort:{

+        testInstanceName:1

+      }

+    }).subscribe(result => {

+      //console.log(result);

+      for (let index in result) {

+        this.testInstances.push({ id: result[index]._id, viewValue: result[index].testInstanceName })

+      }

+      //this.testInstances.sort((a, b) => b.viewValue - a.viewValue);

+    })

+  }

+

+  checkDates() {

+    let allSet = true;

+

+    if (this.scheduleFilters.startDate > this.scheduleFilters.endDate) {

+      allSet = false;

+      alert("Schedule Filters: Your end date cannot be earlier than your start date.");

+    } else if (this.tdFilters.startDate > this.tdFilters.endDate) {

+      allSet = false;

+      alert("Test Definition Filters: Your end date cannot be earlier than your start date.");

+    } else if (this.tiFilters.startDate > this.tiFilters.endDate) {

+      allSet = false;

+      alert("Test Instance Filters: Your end date cannot be earlier than your start date.");

+    }

+    return allSet;

+  }

+

+  onConfirm() {

+    if (this.checkDates() == true) {

+      this.close();

+      this.statsService.filterData(this.allFilters, this.tdFilters, this.tiFilters, this.scheduleFilters);

+      //console.log(this.tdFilters);

+    }

+  }

+

+  close() {

+    this.dialogRef.close();

+  }

+

+}

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
index 0000000..d2f9bbd
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.pug
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div(#horizBarChartDiv, [style.height]="height")

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..79d017d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { HorizBarChartComponent } from './horiz-bar-chart.component';

+

+describe('HorizBarChartComponent', () => {

+  let component: HorizBarChartComponent;

+  let fixture: ComponentFixture<HorizBarChartComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ HorizBarChartComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(HorizBarChartComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..af6175e
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/horiz-bar-chart/horiz-bar-chart.component.ts
@@ -0,0 +1,175 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, NgZone, Input, ViewChild, ElementRef, OnDestroy, } from '@angular/core';

+import * as am4core from "@amcharts/amcharts4/core";

+import * as am4charts from "@amcharts/amcharts4/charts";

+import { StatsService } from '../stats.service'

+

+// am4core.useTheme(am4themes_animated);

+

+@Component({

+  selector: 'app-horiz-bar-chart',

+  templateUrl: './horiz-bar-chart.component.pug',

+  styleUrls: ['./horiz-bar-chart.component.scss']

+})

+export class HorizBarChartComponent implements OnInit, OnDestroy {

+

+  @ViewChild('horizBarChartDiv') HorizBarChartDiv: ElementRef;

+  @Input() height: string;

+

+  public chart: am4charts.XYChart;

+  public testInstanceData;

+  public loadingIndicator;

+  protected stats: StatsService;

+

+  constructor(private statsService: StatsService) {

+    this.stats = statsService;

+  }

+

+

+  ngOnInit() {

+    this.renderChart();

+

+    this.stats.onDefaultDataCallStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    })

+

+    this.stats.onTIExecutionChangeStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    })

+

+    this.stats.onDefaultDataCallFinished().subscribe(res => {

+      this.setChartData();

+    })

+

+    this.stats.onTIExecutionChangeFinished().subscribe(res => {

+      this.setChartData()

+    })

+

+  }

+

+  ngOnDestroy() {

+    this.chart.dispose();

+  }

+

+  showLoadingIndicator() {

+    if(!this.loadingIndicator){

+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);

+      this.loadingIndicator.background.fill = am4core.color("#fff");

+      this.loadingIndicator.background.fillOpacity = 0.8;

+      this.loadingIndicator.width = am4core.percent(100);

+      this.loadingIndicator.height = am4core.percent(100);

+

+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);

+      indicatorLabel.text = "Loading..";

+      indicatorLabel.align = "center";

+      indicatorLabel.valign = "middle";

+      indicatorLabel.fontSize = 18;

+      indicatorLabel.fontWeight= "bold";

+      indicatorLabel.dy = 50;

+

+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);

+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";

+      loadingImage.href = "/assets/images/equalizer.gif";

+      //loadingImage.dataSource = "/loading-pies.svg"

+      loadingImage.align = "center";

+      loadingImage.valign = "middle";

+      loadingImage.horizontalCenter = "middle";

+      loadingImage.verticalCenter = "middle";

+      loadingImage.scale = 3.0;

+    }else{

+      this.loadingIndicator.show();

+    }

+

+  }

+

+  hideLoadingIndicator() {

+    this.loadingIndicator.hide();

+  }

+

+  setChartData() {

+    this.testInstanceData = this.stats.getData('testInstances');

+    this.chart.data = this.testInstanceData;

+

+    // Displays the average time for each bar. 

+    // If there is no time recorded for the Test Instance, display No Time Recorded.

+    let series = this.chart.series.values[0] as am4charts.ColumnSeries;

+    

+    

+    series.columns.template.adapter.add("tooltipText", (text, target) => {

+      if (target.dataItem) {

+        if (this.chart.data[target.dataItem.index].Average > 0) {

+          return this.chart.data[target.dataItem.index].Count.toString() + " Executions \n Avg Time: " + this.chart.data[target.dataItem.index].Average.toFixed(2).toString() + " seconds";

+        } else

+          return this.chart.data[target.dataItem.index].Count.toString() + " Executions \n No Time Recorded";

+      }

+    });

+    series.columns.template.adapter.add("fill", (fill, target) => this.chart.colors.getIndex(target.dataItem.index));

+    // console.log(this.chart.yAxes);

+    this.chart.yAxes.values[0].zoomToIndexes(0, 6, false, true);

+    this.hideLoadingIndicator();

+    

+  }

+

+  renderChart() {

+    this.chart = am4core.create(this.HorizBarChartDiv.nativeElement, am4charts.XYChart);

+    this.chart.cursor = new am4charts.XYCursor();

+    this.showLoadingIndicator();

+

+    this.chart.responsive.enabled = true;

+

+    // Create axes

+    var categoryAxis = this.chart.yAxes.push(new am4charts.CategoryAxis());

+    categoryAxis.dataFields.category = "Name";

+    categoryAxis.numberFormatter.numberFormat = "#";

+    categoryAxis.renderer.inversed = true;

+    categoryAxis.renderer.minGridDistance = 5;

+    categoryAxis.title.text = "Test Instances";

+    categoryAxis.title.fontSize = "10px";

+

+    var valueAxis = this.chart.xAxes.push(new am4charts.ValueAxis());

+    valueAxis.renderer.minWidth = 10;

+

+    // Create series

+    var series = this.chart.series.push(new am4charts.ColumnSeries());

+    series.dataFields.valueX = "Count";

+    series.dataFields.categoryY = "Name";

+    series.columns.template.tooltipText = " ";

+

+    let label = categoryAxis.renderer.labels.template;

+    label.truncate = true;

+    label.maxWidth = 130;

+    label.fontSize = 13;

+

+    //Scrollbar on the right. 

+    let scrollBarY = new am4charts.XYChartScrollbar();

+    scrollBarY.series.push(series);

+    this.chart.scrollbarY = scrollBarY;

+    this.chart.scrollbarY.contentHeight = 100;

+    this.chart.scrollbarY.minWidth = 20;

+    this.chart.scrollbarY.thumb.minWidth = 20;

+

+    //set initial Scrollbar Zoom to the Top 6 Instances. 

+  //   this.chart.events.on("ready", () => {

+  //     console.log("here")

+  //     categoryAxis.zoomToIndexes(0, 6, false, true);

+  //   });

+   }

+

+}

+

+

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
index 0000000..e26fb12
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.pug
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..4201928
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { LineChartComponent } from './line-chart.component';

+

+describe('LineChartComponent', () => {

+  let component: LineChartComponent;

+  let fixture: ComponentFixture<LineChartComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ LineChartComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(LineChartComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..e7f0780
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/line-chart/line-chart.component.ts
@@ -0,0 +1,172 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, ViewChild, ElementRef, OnDestroy } from '@angular/core';

+import { StatsService } from '../stats.service';

+import * as am4core from "@amcharts/amcharts4/core";

+import * as am4charts from "@amcharts/amcharts4/charts";

+import { _ } from 'ag-grid-community';

+import * as moment from 'moment';

+import { Subscription } from 'rxjs';

+

+// am4core.useTheme(am4themes_animated);

+

+@Component({

+  selector: 'app-line-chart',

+  templateUrl: './line-chart.component.pug',

+  styleUrls: ['./line-chart.component.scss']

+})

+export class LineChartComponent implements OnInit, OnDestroy {

+

+  private toDestroy: Array<Subscription> = [];

+

+  @ViewChild('linechartdiv') LineChartDiv: ElementRef;

+  @Input() height: string;

+

+  //public testDefinitionName = "Hello";

+  private chart: am4charts.XYChart;

+  private loadingIndicator;

+

+  constructor(private stats: StatsService) {

+  }

+

+  ngOnInit() {

+

+    this.renderChart();

+

+    this.toDestroy.push(this.stats.onTDExecutionChangeStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {

+      this.setChartData();

+    }));

+

+    this.toDestroy.push(this.stats.onTDExecutionChangeFinished().subscribe(res => {

+      this.setChartData();

+    }));

+

+  }

+

+  ngOnDestroy(){

+    //destory chart

+    this.chart.dispose();

+  }

+

+  //Sets count to 0 for any dates that dont have data

+  setupPoints(rawData) {

+    

+    let formattedData = []; 

+    let dayRange = moment(this.stats.filters.endDate).add(1, 'days').diff(moment(this.stats.filters.startDate), 'days');

+    

+    for(let i = 0; i < dayRange; i++){

+      //find date in raw data

+      let d = rawData.find(e => moment(e.date).isSame(moment(this.stats.filters.startDate).add(i, 'days'), 'day'));

+      let count = 0;

+      if(d){

+        count = d.count;

+      }

+      formattedData.push({

+        date: moment(this.stats.filters.startDate).startOf('day').add(i, 'days').toDate(),

+        count: count

+      })

+    }

+

+    return formattedData;

+  }

+

+  showLoadingIndicator() {

+

+    //this.height = "380px";

+    if(!this.loadingIndicator){

+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);

+      this.loadingIndicator.background.fill = am4core.color("#fff");

+      this.loadingIndicator.background.fillOpacity = 0.8;

+      this.loadingIndicator.width = am4core.percent(100);

+      this.loadingIndicator.height = am4core.percent(100);

+

+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);

+      indicatorLabel.text = "Loading..";

+      indicatorLabel.align = "center";

+      indicatorLabel.valign = "middle";

+      indicatorLabel.fontSize = 18;

+      indicatorLabel.fontWeight = "bold";

+      indicatorLabel.dy = 50;

+

+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);

+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";

+      loadingImage.href = "/assets/images/equalizer.gif";

+      //loadingImage.dataSource = "/loading-pies.svg"

+      loadingImage.align = "center";

+      loadingImage.valign = "middle";

+      loadingImage.horizontalCenter = "middle";

+      loadingImage.verticalCenter = "middle";

+      loadingImage.scale = 3.0;

+    }else{

+      this.loadingIndicator.show();

+    }

+  }

+

+  hideLoadingIndicator() {

+    this.loadingIndicator.hide();

+  }

+

+  setChartData() {

+    let executions = this.stats.getData('TD_Executions');

+    this.chart.data = this.setupPoints(executions);

+

+    this.hideLoadingIndicator();

+  }

+

+  renderChart() {

+

+    if(this.chart){

+      this.chart.dispose();

+    }

+    this.chart = am4core.create(this.LineChartDiv.nativeElement, am4charts.XYChart);

+    this.chart.preloader.disabled = true;

+    this.showLoadingIndicator();

+

+    let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());

+    dateAxis.fontSize = "10px";

+

+    let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());

+    valueAxis.title.text = "Executions";

+    valueAxis.title.fontSize = "10px";

+

+    let series = this.chart.series.push(new am4charts.LineSeries());

+    series.dataFields.dateX = "date";

+    series.dataFields.valueY = "count";

+    series.strokeWidth = 3;

+

+    series.fillOpacity = .5;

+    // series.tensionX = 0.8;

+    series.sequencedInterpolation = false;

+    series.tooltipText = "{valueY.value}";

+

+    this.chart.cursor = new am4charts.XYCursor();

+

+    this.chart.responsive.enabled = true;

+  }

+

+

+

+

+}

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
index 0000000..940e640
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.pug
@@ -0,0 +1,19 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+p(style="width: 100%; text-align: center;") Test Instance Executions over time

+hr

+div(#multilinechartdiv, [style.height]="height")

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..e43604d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { MultiLineChartComponent } from './multi-line-chart.component';

+

+describe('MultiLineChartComponent', () => {

+  let component: MultiLineChartComponent;

+  let fixture: ComponentFixture<MultiLineChartComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ MultiLineChartComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(MultiLineChartComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..e9782c4
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/multi-line-chart/multi-line-chart.component.ts
@@ -0,0 +1,336 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';

+import { StatsService } from '../stats.service';

+import * as am4core from "@amcharts/amcharts4/core";

+import * as am4charts from "@amcharts/amcharts4/charts";

+import * as moment from 'moment';

+

+//am4core.useTheme(am4themes_animated);

+

+@Component({

+  selector: 'app-multi-line-chart',

+  templateUrl: './multi-line-chart.component.pug',

+  styleUrls: ['./multi-line-chart.component.scss']

+})

+export class MultiLineChartComponent implements OnInit {

+

+  @ViewChild('multilinechartdiv') MultiLineChartDiv: ElementRef;

+  @Input() height: string;

+

+  public chart: am4charts.XYChart;

+  public loadingIndicator;

+  public chartData;

+  protected stats: StatsService;

+  public dataIsEmpty = 0;

+  constructor(private statsService: StatsService) {

+    this.stats = statsService;

+  }

+

+  ngOnInit() {

+

+    this.stats.onDefaultDataCallStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    })

+

+    this.stats.onTIExecutionChangeStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    })

+

+    this.stats.onDefaultDataCallFinished().subscribe(res => {

+      this.renderChart();

+      this.hideLoadingIndicator();

+    })

+

+    this.stats.onTIExecutionChangeFinished().subscribe(res => {

+      this.renderChart();

+      this.hideLoadingIndicator();

+    })

+    this.renderChart();

+

+    //Resize if screen size changes.

+    // this.stats.checkWindow().subscribe(res=>{

+    //   this.renderChart();

+    // })

+  }

+

+

+  // 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.

+  reformatData() {

+    var newData = [];

+

+    //Names of the test instances. 

+    var InstanceStrings = {};

+

+    //Go through the instances and add the names to the InstanceStrings Array. 

+    for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {

+      var instanceName = this.chartData[numberInstances].testInstanceName;

+      InstanceStrings[instanceName] = 0;

+    }

+

+    // Iterate through the test instances. 

+    for (var instanceLength = 0; instanceLength < this.chartData.length; instanceLength++) {

+

+      //For each instance go through the dates. 

+      for (var numDates = 0; numDates < this.chartData[instanceLength].dateData.length; numDates++) {

+

+        //Check newData to see if date has been pushed. 

+        var dateIndex = newData.findIndex((element) => {

+          return (

+            this.chartData[instanceLength].dateData[numDates].date.getFullYear() === element.date.getFullYear() &&

+            this.chartData[instanceLength].dateData[numDates].date.getMonth() === element.date.getMonth() &&

+            this.chartData[instanceLength].dateData[numDates].date.getDate() === element.date.getDate()

+          )

+        });

+

+        //If date is not present push the new date and the count for the test instance. 

+        if (newData[dateIndex] == undefined) {

+          //date is not present

+          var newDate = {

+            date: new Date(this.chartData[instanceLength].dateData[numDates].date.getFullYear(),

+              this.chartData[instanceLength].dateData[numDates].date.getMonth(),

+              this.chartData[instanceLength].dateData[numDates].date.getDate())

+          };

+          newDate = Object.assign(newDate, InstanceStrings);

+          newDate[this.chartData[instanceLength].testInstanceName] = this.chartData[instanceLength].dateData[numDates].count;

+          newData.push(newDate);

+        } else {

+

+          //If the date is present update the count for that test instance. 

+          newData[dateIndex][this.chartData[instanceLength].testInstanceName] += this.chartData[instanceLength].dateData[numDates].count;

+        }

+      }

+    }

+    return newData;

+  }

+

+  //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. 

+  async setupPoints(rawData): Promise<any> {

+    let formattedData = [];

+

+    //If the chart is supposed to be empty push in a line with a count of 0.

+    if (rawData.length == 0) {

+      return new Promise((resolve, reject) => {

+

+        let days = this.daysDuration(this.stats.filters.startDate, this.stats.filters.endDate);

+

+        if (!days) {

+          days = 62;

+          this.stats.filters.startDate = (moment().subtract(2, "months").toDate());

+        }

+

+        // Go through 62 days and push a count of 0/

+        for (let day = 0; day < days; day++) {

+          let newDate = this.addDaysToDate(this.stats.filters.startDate, day);

+          formattedData.push({

+            date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),

+            count: 0,

+            testInstancename: "empty"

+          })

+        }

+        resolve(formattedData);

+      })

+

+

+

+

+

+      //Data is not empty. push in empty days for each instance. Some instances might not have executions for each day. 

+    } else return new Promise((resolve, reject) => {

+      //get list of test instances. 

+      var InstanceStrings = {};

+      for (var numberInstances = 0; numberInstances < this.chartData.length; numberInstances++) {

+        var instanceName = this.chartData[numberInstances].testInstanceName;

+        InstanceStrings[instanceName] = 0;

+      }

+

+

+      //Go through the data

+      for (let i = 0; i < rawData.length; i++) {

+

+        //for the first iteration, 

+        if (i == 0) {

+          formattedData.push(rawData[0]);

+

+          // if the date is before the startDate specified by the filter or two months be default. 

+          if (formattedData[0].date > this.stats.filters.startDate) {

+

+            // Go through the difference in days and push the date and count of 0. 

+            let days = this.daysDuration(this.stats.filters.startDate, formattedData[0].date)

+            for (let k = days - 1; k >= 0; k--) {

+              let newDate = this.addDaysToDate(this.stats.filters.startDate, k);

+              var objectToAdd = {

+                date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),

+              };

+              //push the new date and all the strings for the test instances.

+              objectToAdd = Object.assign(objectToAdd, InstanceStrings);

+

+              //add date to the beginning of the array. 

+              formattedData.unshift(objectToAdd)

+

+            }

+          }

+

+          //for all other iterations

+        } else {

+

+          //get the difference in days.

+          let days = this.daysDuration(rawData[i].date, rawData[i - 1].date);

+          if (days > 1) {

+            //push the new dates. 

+            for (let j = 1; j < days; j++) {

+              let newDate = this.addDaysToDate(rawData[i - 1].date, j);

+              var objectToAdd = {

+                date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),

+              };

+              //push the new date and all the strings for the test instances.

+              objectToAdd = Object.assign(objectToAdd, InstanceStrings);

+              formattedData.push(objectToAdd);

+            }

+          }

+          formattedData.push(rawData[i]);

+        }

+      }

+

+      if (rawData.length >= 1) {

+        var days = this.daysDuration(rawData[rawData.length - 1].date, this.stats.filters.endDate);

+        if (days >= 1) {

+          for (let j = 1; j < days; j++) {

+            let newDate = this.addDaysToDate(rawData[rawData.length - 1].date, j);

+            var objectToAdd = {

+              date: new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()),

+            };

+            objectToAdd = Object.assign(objectToAdd, InstanceStrings);

+            formattedData.push(objectToAdd);

+          }

+        }

+      }

+

+

+      resolve(formattedData);

+    })

+  }

+

+  daysDuration(date1, date2) {

+    return Math.ceil(Math.abs((date1 - date2) / (60 * 60 * 24 * 1000)));

+  }

+

+  addDaysToDate(date, days) {

+    let newDate = new Date(date);

+    newDate.setDate(date.getDate() + days);

+    return newDate;

+  }

+

+  //initialize loading indicator

+  showLoadingIndicator() {

+

+    this.height = "380px";

+    this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);

+    this.loadingIndicator.background.fill = am4core.color("#fff");

+    this.loadingIndicator.background.fillOpacity = 0.8;

+    this.loadingIndicator.width = am4core.percent(100);

+    this.loadingIndicator.height = am4core.percent(100);

+

+    let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);

+    indicatorLabel.text = "Loading..";

+    indicatorLabel.align = "center";

+    indicatorLabel.valign = "middle";

+    indicatorLabel.fontSize = 18;

+    indicatorLabel.fontWeight= "bold";

+    indicatorLabel.dy = 50;

+

+    let loadingImage = this.loadingIndicator.createChild(am4core.Image);

+    //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";

+    loadingImage.href = "https://loading.io/spinners/equalizer/lg.equalizer-bars-loader.gif";

+    //loadingImage.dataSource = "/loading-pies.svg"

+    loadingImage.align = "center";

+    loadingImage.valign = "middle";

+    loadingImage.horizontalCenter = "middle";

+    loadingImage.verticalCenter = "middle";

+    loadingImage.scale = 3.0;

+

+  }

+

+  hideLoadingIndicator() {

+    this.loadingIndicator.hide();

+  }

+

+  async renderChart() {

+    //console.log("here")

+

+    am4core.options.minPolylineStep = 5;

+

+    this.chart = am4core.create(this.MultiLineChartDiv.nativeElement, am4charts.XYChart);

+    this.chart.hiddenState.properties.opacity = 0; // this creates initial fade-in

+

+    this.chart.paddingRight = 20;

+    this.chartData = this.stats.getData("multiLineData");

+

+    //reformat the data to match the format needed for amcharts. 

+    var formattedData = this.reformatData();

+

+    //sort the data.

+    formattedData.sort((a, b) => a.date - b.date);

+

+    //fill in gaps in the data

+    await this.setupPoints(formattedData).then(res => {

+      formattedData = res;

+    }, err => console.log(err));

+

+    this.chart.data = formattedData;

+

+    let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());

+    dateAxis.title.text = "Date";

+

+    let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());

+    valueAxis.title.text = "Executions";

+

+    this.chart.cursor = new am4charts.XYCursor();

+    this.chart.cursor.xAxis = dateAxis;

+

+    //if the data is empty, push in a line and set the count to 0. 

+    if (this.chartData.length == 0) {

+      this.chartData.push({ testInstanceName: "empty" })

+      let newSeries = this.chart.series.push(new am4charts.LineSeries());

+      newSeries.name = "empty";

+      newSeries.dataFields.dateX = "date";

+      newSeries.dataFields.valueY = "count";

+      newSeries.tooltipText = "{valueY.value}";

+      newSeries.sequencedInterpolation = true;

+      newSeries.defaultState.transitionDuration = 1000;

+      newSeries.strokeWidth = 3;

+      newSeries.tensionX = 0.8;

+    } else {

+

+      //initialize the lines for the series

+      for (let index = 0; index < this.chartData.length; index++) {

+        let newSeries = this.chart.series.push(new am4charts.LineSeries());

+        newSeries.name = this.chartData[index].testInstanceName;

+        newSeries.dataFields.dateX = "date";

+        newSeries.dataFields.valueY = (this.chartData[index].testInstanceName).toString();

+        newSeries.tooltipText = "{valueY.value}";

+        newSeries.sequencedInterpolation = true;

+        newSeries.defaultState.transitionDuration = 1000;

+        newSeries.strokeWidth = 3;

+        newSeries.tensionX = 0.8;

+      }

+      this.chart.legend = new am4charts.Legend();

+      this.chart.legend.labels.template.text = "[bold {color}]{name}";

+    }

+  }

+

+}
\ 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
index 0000000..0456731
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.pug
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..28b4c37
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { PieChartComponent } from './pie-chart.component';

+

+describe('PieChartComponent', () => {

+  let component: PieChartComponent;

+  let fixture: ComponentFixture<PieChartComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ PieChartComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(PieChartComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..16e7166
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/pie-chart/pie-chart.component.ts
@@ -0,0 +1,181 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, NgZone, Input, ViewChild, ElementRef, OnDestroy, } from '@angular/core';

+import * as am4core from "@amcharts/amcharts4/core";

+import * as am4charts from "@amcharts/amcharts4/charts";

+import { StatsService } from '../stats.service';

+import { Router } from '@angular/router';

+import { Subscription } from 'rxjs';

+

+//am4core.useTheme(frozen);

+//am4core.useTheme(am4themes_animated);

+

+@Component({

+  selector: 'app-pie-chart',

+  templateUrl: './pie-chart.component.pug',

+  styleUrls: ['./pie-chart.component.scss']

+})

+export class PieChartComponent implements OnInit, OnDestroy {

+

+  private toDestroy: Array<Subscription> = [];

+

+  @ViewChild('pieChartDiv') PieChartDiv: ElementRef;

+  @ViewChild('legendDiv') legendDiv: ElementRef;

+  @Input() height: string;

+

+  protected stats: StatsService;

+  public chart: am4charts.PieChart;

+  private chartData: Array<Object>;

+  public loadingIndicator;

+

+  constructor(private statsService: StatsService, private route: Router) {

+    this.stats = statsService;

+  }

+

+  ngOnInit() {

+

+    this.renderChart();

+

+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onTDExecutionChangeStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {

+      this.setChartData();

+    }));

+

+    this.toDestroy.push(this.stats.onTDExecutionChangeFinished().subscribe(res => {

+      this.setChartData()

+    }));

+    

+

+    // //Resize if screen size changes.

+    // this.stats.checkWindow().subscribe(res=>{

+    //   this.renderChart();

+    // })

+

+  }

+

+  ngOnDestroy() {

+    this.toDestroy.forEach(e => e.unsubscribe());

+    this.chart.dispose();

+  }

+

+  showLoadingIndicator() {

+

+    if(!this.loadingIndicator){

+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);

+      this.loadingIndicator.background.fill = am4core.color("#fff");

+      this.loadingIndicator.background.fillOpacity = 0.8;

+      this.loadingIndicator.width = am4core.percent(100);

+      this.loadingIndicator.height = am4core.percent(100);

+

+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);

+      indicatorLabel.text = "Loading..";

+      indicatorLabel.align = "center";

+      indicatorLabel.valign = "middle";

+      indicatorLabel.fontSize = 18;

+      indicatorLabel.fontWeight= "bold";

+      indicatorLabel.dy = 50;

+

+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);

+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";

+      loadingImage.href = "/assets/images/equalizer.gif";

+      //loadingImage.dataSource = "/loading-pies.svg"

+      loadingImage.align = "center";

+      loadingImage.valign = "middle";

+      loadingImage.horizontalCenter = "middle";

+      loadingImage.verticalCenter = "middle";

+      loadingImage.scale = 3.0;

+    }else{

+      this.loadingIndicator.show();

+    }

+    

+

+  }

+

+  hideLoadingIndicator() {

+    this.loadingIndicator.hide();

+  }

+

+  setChartData(){

+    this.chartData = this.stats.getData("TD_Results") as Array<Object>;

+    if (this.chartData.length == 0) {

+      this.chart.data = [{

+        Name: "N/A",

+        Count: 1,

+      }]

+      

+      this.chart.series.values[0].tooltipText = "No Executions Found"

+    } else {

+      this.chart.data = this.chartData;

+      //OnClick open page for that result. 

+      this.chart.series.values[0].slices.template.events.on("doublehit", (clickedSlice) => {

+        this.route.navigate(['/test-executions', { filter: clickedSlice.target.dataItem.dataContext['Name'].toString().toLowerCase() }]);

+      });

+    }

+    this.hideLoadingIndicator();

+    

+  }

+

+  renderChart() {

+

+    this.chart = am4core.create(this.PieChartDiv.nativeElement, am4charts.PieChart);

+    let series = this.chart.series.push(new am4charts.PieSeries());

+    this.chart.scale = 1;

+    this.chart.align = "center";

+    this.showLoadingIndicator();

+

+    // this.chart.legend = new am4charts.Legend();

+    // this.chart.legend.position = "right";

+    // this.chart.legend.scale = .7;

+    // this.chart.legend.markers.template.width = 10;

+    // this.chart.legend.markers.template.height = 10;

+  

+    //chart.preloader.disabled = false;

+    //chart.hiddenState.properties.opacity = 0; // this creates initial fade-in

+    

+    // var legendContainer = am4core.create(this.legendDiv.nativeElement, am4core.Container);

+    // legendContainer.width = am4core.percent(100);

+    // legendContainer.height = am4core.percent(100);

+    // this.chart.legend.parent = legendContainer;

+    series.dataFields.value = "Count";

+    series.dataFields.category = "Name";

+    series.slices.template.strokeWidth = 1;

+    series.slices.template.strokeOpacity = 1;

+    series.slices.template.propertyFields.fill = "color"

+    series.scale = .8;

+

+    // This creates initial animation

+    series.hiddenState.properties.opacity = 1;

+    series.hiddenState.properties.endAngle = -90;

+    series.hiddenState.properties.startAngle = -90;

+    series.ticks.template.disabled = false;

+    series.labels.template.disabled = false;

+    series.titleElement.textContent = 'Total Test Results'

+

+    //responsive pie chart. if size of chart is less than 450 pixels remove legend. 

+    this.chart.responsive.enabled = true;

+    

+    

+

+  }

+}

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
index 0000000..b612b28
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.pug
@@ -0,0 +1,34 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+hr

+div(*ngIf=dataSource).container

+  table(mat-table [dataSource]="dataSource")

+    ng-container(matColumnDef="name")

+      th(mat-header-cell *matHeaderCellDef) Name

+      td(mat-cell *matCellDef='let element') {{element.name}}

+

+    ng-container(matColumnDef="dateExec")

+      th(mat-header-cell *matHeaderCellDef) Date

+      td(mat-cell *matCellDef='let element') {{element.dateExec}}

+

+    ng-container(matColumnDef="timeExec")

+      th(mat-header-cell *matHeaderCellDef) Time

+      td(mat-cell *matCellDef='let element') {{element.timeExec}}

+

+

+    tr(mat-header-row *matHeaderRowDef="displayedColumns")

+    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
index 0000000..1291718
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.scss
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.container{

+    overflow: auto;

+}

+

+table{

+    height: 100%;

+    width: 100%;

+}

+

+td, th{

+    font-size: 14px;

+}
\ 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
index 0000000..14cb457
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { ScheduleComponent } from './schedule.component';

+

+describe('ScheduleComponent', () => {

+  let component: ScheduleComponent;

+  let fixture: ComponentFixture<ScheduleComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ ScheduleComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(ScheduleComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..a8a04ce
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/schedule/schedule.component.ts
@@ -0,0 +1,71 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, NgZone, ChangeDetectorRef } from '@angular/core';

+//import material from "@amcharts/amcharts4/themes/material";

+import am4themes_animated from "@amcharts/amcharts4/themes/animated";

+import { GroupService } from 'app/shared/services/group.service';

+import { StatsService } from '../stats.service';

+import { Observable, Subject } from 'rxjs';

+

+export interface ScheduleElement {

+  name: string;

+  dateExec: string;

+  timeExec: string;

+}

+

+@Component({

+  selector: 'app-schedule',

+  templateUrl: './schedule.component.pug',

+  styleUrls: ['./schedule.component.scss']

+})

+

+export class ScheduleComponent implements OnInit {

+

+  protected stats: StatsService;

+  public doneLoadingfalse;

+  public dataSource;

+

+  displayedColumns: string[] = ['name', 'dateExec', 'timeExec'];

+

+  constructor(private zone: NgZone, private _groups: GroupService, private statsService: StatsService, private changeDetector: ChangeDetectorRef) {

+    this.stats = statsService;

+  }

+

+  ngOnInit() {

+

+    this.stats.onDefaultDataCallFinished().subscribe(res => {

+      this.dataSource = this.stats.getData("Schedule");

+    })

+    this.dataSource = this.stats.getData("Schedule");

+

+    this.refresh();

+  }

+

+  defaultDataListener(): Observable<Object> {

+    return this.stats.finishedDefaultData;

+  }

+

+  refresh(){

+    this.stats.onScheduleChangeFinished().subscribe(res => {

+      this.dataSource = this.stats.getData("Schedule");

+      this.dataSource = this.dataSource.slice();

+      

+      this.changeDetector.detectChanges();

+    })

+  }

+

+}
\ 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
index 0000000..59cd997
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/stats.service.spec.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed } from '@angular/core/testing';

+

+import { StatsService } from './stats.service';

+

+describe('StatsService', () => {

+  beforeEach(() => TestBed.configureTestingModule({}));

+

+  it('should be created', () => {

+    const service: StatsService = TestBed.get(StatsService);

+    expect(service).toBeTruthy();

+  });

+});

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
index 0000000..31d872c
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/stats.service.ts
@@ -0,0 +1,765 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { FeathersService } from 'app/shared/services/feathers.service';

+import { TestExecutionService } from 'app/shared/services/test-execution.service';

+import { Observable, Subject, from } from 'rxjs';

+import { MatDialog } from '@angular/material';

+import { GroupService } from 'app/shared/services/group.service';

+import * as moment from 'moment';

+import { SchedulingService } from 'app/shared/services/scheduling.service';

+import { TestInstanceService } from 'app/shared/services/test-instance.service';

+import { string, number } from '@amcharts/amcharts4/core';

+import { group } from '@angular/animations';

+export interface StatsFilter {

+  startDate?: Date,

+  endDate?: Date,

+  selectedTestDefinitions?: Array<any>,

+  selectedTestInstances?: Array<any>

+}

+

+@Injectable({

+  providedIn: 'root'

+})

+

+//this service serves as the controller between the dashboard's data management and the components' UI.

+export class StatsService {

+

+  //set default filters

+  public filters: StatsFilter = {

+    startDate: moment().subtract(1, 'weeks').toDate(),

+    endDate: moment().toDate()

+  }

+

+  public executionList: Array<any> = [];

+

+  public testDefinitionData = {

+    //Executions Array thats made of objects with date, test definition name, and count

+    "Executions": [],

+    //Array of Results for the Pie Chart

+    "Results": [],

+  }

+  public testInstanceData = {

+    //Executions for Each Test Instance

+    "Executions": [],

+    //For multilinechart, objects made of test instance names, and execution count for each one.

+    "Individual_Exec": []

+  };

+

+  //list if test instance names

+  public testInstances = [];

+  //list of scheduled tests gotten from agenda in db.

+  public scheduledTests = [];

+

+  //these are filter objects attached to stats service that are updated whenever user confirms from filter modal. 

+  //They are updated in the filter functions below.

+  // public tdFilters = {

+  //   startDate: {},

+  //   endDate: {},

+  //   selected: [],

+  // }

+  // public tiFilters = {

+  //   startDate: {},

+  //   endDate: {},

+  //   selectedTDs: [],

+  //   selectedTIs: [],

+  //   //this limit is for the amount of multiple series being displayed on the multiline chart according to default/filters.

+  //   multiLineLimit: 5,

+  // }

+  // public scheduleFilters = {

+  //   startDate: {},

+  //   endDate: {},

+  //   timeRangeStart: {},

+  //   timeRangeEnd: {},

+  //   selectedInstances: [],

+  // }

+

+  //these are for triggering the listeners located in the chart components.

+  //executionsChange subjects are triggered when user filters.

+  public tdExecutionsChange: Subject<Object> = new Subject<Object>();

+  public tiExecutionsChange: Subject<Object> = new Subject<Object>();

+  public scheduleChange: Subject<Object> = new Subject<Object>();

+  public finishedDefaultData: Subject<Object> = new Subject<Object>();

+  public startDefaultData: Subject<Object> = new Subject<Object>();

+  public startTDExecutionCall: Subject<Object> = new Subject<Object>();

+  public startTIExecutionCall: Subject<Object> = new Subject<Object>();

+  public windowResized: Subject<Object> = new Subject<Object>();

+  public startScheduleCall: Subject<Object> = new Subject<Object>();

+

+  constructor(public feathers: FeathersService, public testExecution: TestExecutionService,

+    public _groups: GroupService, public testInstanceService: TestInstanceService, public schedService: SchedulingService) {

+

+    //listening for whether user changes group, if so, variables are reset (rest are inside defaultData, we can work on consistency).

+    //and we get default data for the new group passed in.

+    // this.getDefaultData(this._groups.getGroup());

+    // this._groups.groupChange().subscribe(group => {

+    //   this.getDefaultData(group);

+    // });

+

+  }

+

+  

+

+  //these are trigger functions that we call in the data manipulating functions below.

+  //the purpose of these functions is to let the listeners set up in the chart components know that the data has been updated.

+  //then the chart components recall the data using getData().

+  checkWindow() {

+    return this.windowResized;

+  }

+

+  onDefaultDataCallStarted() {

+    return this.startDefaultData;

+  }

+

+  onTDExecutionChangeStarted() {

+    return this.startTDExecutionCall;

+  }

+

+  onTIExecutionChangeStarted() {

+    return this.startTIExecutionCall;

+  }

+

+  onScheduleChangeStarted() {

+    return this.startScheduleCall;

+  }

+

+  onDefaultDataCallFinished() {

+    return this.finishedDefaultData;

+  }

+

+  onTDExecutionChangeFinished() {

+    return this.tdExecutionsChange;

+  }

+

+  onTIExecutionChangeFinished() {

+    return this.tiExecutionsChange;

+  }

+

+  onScheduleChangeFinished() {

+    return this.scheduleChange;

+  }

+

+  //one giant getter where we pass in the name of what we want and switch case it.

+

+  //This function is called in the components and returns the relavent array for the component.

+  getData(name: string) {

+    let outputData = {};

+

+    switch (name) {

+      case "TD_Executions":

+        return this.testDefinitionData.Executions;

+      case "TD_Results":

+        return this.testDefinitionData.Results;

+      case "testInstances":

+        return this.testInstanceData.Executions;

+      case "Schedule":

+        return this.scheduledTests;

+      // case "multiLineData":

+      //   //limitting the series being displayed.

+      //   return this.testInstanceData.Individual_Exec.slice(0, this.tiFilters.multiLineLimit);

+    }

+    //console.log(outputData);

+    return outputData;

+  }

+

+  //this gets called from the filter modal when the user confirms their filters.

+  filterData(allFilters, tdFilters, tiFilters, schedFilters) {

+    //this.filterAll(allFilters);

+    this.filterTDs(tdFilters);

+    this.filterTIs(tiFilters);

+    this.filterSchedule(schedFilters)

+  }

+

+  //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.

+  filterAll(allFilters) {

+    //console.log('Filtering everything')

+    //console.log(allFilters);

+    if (allFilters.startDate != "" || allFilters.endDate != "") {

+      if (allFilters.endDate == "") {

+        this.testDefinitionData.Executions = this.testDefinitionData.Executions.filter(execution => (

+          execution.startTime >= allFilters.startDate

+        ))

+      } else if (allFilters.startDate == "") {

+        this.testDefinitionData.Executions = this.testDefinitionData.Executions.filter(execution => (

+          execution.startTime <= allFilters.endDate

+        ))

+      } else {

+        this.testDefinitionData.Executions = this.testDefinitionData.Executions.filter(execution => (

+          execution.startTime >= allFilters.startDate &&

+          execution.date <= allFilters.endDate

+        ))

+      }

+    }

+  }

+

+  /*

+    this function takes in test definition filters and queries data accordingly.

+    improvement needed: if the filters provided do not require querying at all, the function should narrow the currently existing data. This

+    will be faster than requerying in those cases and improve loading times.

+  */

+  async filterTDs(tdFilters) {

+

+    /*

+      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.

+      these start..Call() functions are so chart components can turn on their loading indicators.

+    */

+    // if (tdFilters.startDate == "" && tdFilters.endDate == "" && tdFilters.selected.length == 0) return;

+    // else this.startTDExecutionCall.next(tdFilters);

+

+    // //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.

+    // this.tdFilters = tdFilters;

+

+    // //if no range is passed in we use the default range of past 2 months.

+    // let startDate = tdFilters.startDate == "" ? new Date(moment().subtract(2, 'weeks').format('L')) : new Date(tdFilters.startDate);

+    // let endDate = tdFilters.endDate == "" ? moment().toDate() : new Date(tdFilters.endDate);

+    // //update service filters accordingly. 

+    // this.tdFilters.startDate = startDate;

+    // this.tdFilters.endDate = endDate;

+    //variable of id's of the test definitions selected in the filters.

+    let selectedIDs = this.filters.selectedTestDefinitions.map(item => item.id);

+

+    //Promise that queries the data according the filters. We use a promise so we wait for the data to query before the components render.

+    await new Promise((resolve, reject) => {

+

+      //feathers query time.

+      this.testExecution.find({

+        //get the data relevant to the group.

+        groupId: this._groups.getGroup()["_id"],

+        //thse are gonna go in the data objects.

+        $select: [

+          'historicTestDefinition.testName',

+          'startTime',

+          'testResult'

+        ],

+        //UNLIMITED DATA BABY.

+        $limit: -1,

+        //sort according to ascending dates.

+        $sort: {

+          startTime: 1,

+        },

+        //select the start and end dates from the filter dates.

+        startTime: {

+          $gte: this.filters.startDate,

+          $lte: this.filters.endDate,

+        },

+        //select the test definitions according to the selected ones in the filters.

+        'historicTestDefinition._id': {

+          $in: selectedIDs

+        }

+      }).subscribe(result => {

+

+        //console.log(result)

+

+        //resetting real quick cuz why not.

+        this.testDefinitionData = {

+          "Executions": [],

+          "Results": [],

+        }

+

+        //pretty self explanitory.

+        let fetchedData = result as Array<any>;

+        let currentExecutionsData = this.testDefinitionData.Executions;

+

+        /*

+          for each new fetched json we got with the selected stuff we specified in the feathers query,

+          we need to organize and distribute them accordingly to our service's data objects. For example, the json objects consist of

+          'historicTestDefinition.testName',

+          'startTime',

+          'testResult',

+          test results belong in the results array, the rest needs to be organzied in the executions array,

+          thats what the populate methods are for.

+        */

+        for (let index in fetchedData) {

+          let newItem = fetchedData[index];

+

+          //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

+          //inside itself.

+          this.populateLineChartData(newItem, currentExecutionsData);

+          this.populatePieChartData(newItem);

+        }

+        resolve();

+      })

+    }).then(res => {

+      //console.log(res);

+

+      //trigger that querying is done and the test definition executions data has been changed. Line chart and pie chart listen for this.

+      this.tdExecutionsChange.next(res);

+    })

+  }

+

+  //similar stuffies. just small differences.

+  async filterTIs(tiFilters) {

+

+    // if (tiFilters.startDate == "" && tiFilters.endDate == "" && tiFilters.selectedTDs.length == 0 && tiFilters.selectedTIs.length == 0) return;

+    // else this.startTIExecutionCall.next(tiFilters);

+

+    // this.tiFilters = tiFilters;

+    // if (tiFilters.selectedTIs.length > 0 && tiFilters.selectedTDs.length > 0) this.tiFilters.multiLineLimit = tiFilters.selectedTIs.length + tiFilters.selectedTDs.length;

+    // else if (tiFilters.selectedTIs.length > 0) this.tiFilters.multiLineLimit = tiFilters.selectedTIs.length;

+    // else if (tiFilters.selectedTDs.length > 0) this.tiFilters.multiLineLimit = tiFilters.selectedTDs.length;

+    // else this.tiFilters.multiLineLimit = 5;

+

+    // let startDate = tiFilters.startDate == "" ? new Date(moment().subtract(2, 'weeks').format('L')) : new Date(tiFilters.startDate);

+    // let endDate = tiFilters.endDate == "" ? moment().toDate() : new Date(tiFilters.endDate);

+

+    // this.tiFilters.startDate = startDate;

+    // this.tiFilters.endDate = endDate;

+    // console.log(tiFilters.selectedTDs)

+

+    await new Promise((resolve, reject) => {

+      this.testExecution.find({

+        groupId: this._groups.getGroup()["_id"],

+        $limit: -1,

+        startTime: {

+          $gte: this.filters.startDate,

+          $lte: this.filters.endDate,

+        },

+        $select: [

+          'startTime',

+          'endTime',

+          'historicTestDefinition.testName',

+          'historicTestInstance.testInstanceName',

+        ],

+        $or: [

+          {

+            'historicTestDefinition._id': {

+              $in: tiFilters.selectedTDs

+            }

+          },

+          {

+            'historicTestInstance._id': {

+              $in: tiFilters.selectedTIs

+            }

+          }

+        ]

+

+      }).subscribe(result => {

+        this.testInstanceData = {

+          "Executions": [],

+          "Individual_Exec": []

+        }

+        //console.log(result)

+        let fetchedData = result as Array<any>;

+        for (let index in fetchedData) {

+          let newItem = fetchedData[index];

+          this.populateBarChartData(newItem);

+          this.populateMultiLineChartData(newItem);

+        }

+        this.testInstanceData.Executions.sort((a, b) => b.Count - a.Count);

+        this.testInstanceData.Individual_Exec.sort((a, b) => b.total - a.total);

+

+        resolve();

+      })

+    }).then(res => {

+      this.tiExecutionsChange.next(res);

+      //console.log(this.testInstanceData.Executions);

+    })

+

+  }

+

+  //similar stuffies just smol differneces.

+  async filterSchedule(schedFilters) {

+

+    //console.log(schedFilters);

+    // this.scheduleFilters = schedFilters;

+    // //console.log(schedFilters.selectedInstances);

+

+    // if (schedFilters.startDate == "" &&

+    //   schedFilters.endDate == "" &&

+    //   schedFilters.selectedInstances.length == 0) {

+    //   return;

+    // } else this.startScheduleCall.next(schedFilters);

+

+    // let startDate = schedFilters.startDate == "" ? new Date(moment().toDate()) : new Date(schedFilters.startDate);

+    // let endDate = schedFilters.endDate == "" ? new Date(moment().add(2, 'weeks').format('L')) : new Date(schedFilters.endDate);

+

+    // this.scheduleFilters.startDate = startDate;

+    // this.scheduleFilters.endDate = endDate;

+

+    

+    this.schedService.find({

+      $select: ["data.testSchedule._testInstanceId", 'nextRunAt'],

+      $limit: -1,

+    }).subscribe(result => {

+      this.scheduledTests = [];

+      //console.log(result);

+      let fetchedData = result as Array<any>;

+      let resultingData: Array<any> = fetchedData;

+      if (schedFilters.selectedInstances.length !== 0) {

+        resultingData = fetchedData.filter(el => {

+          let fetchedID = el.data.testSchedule._testInstanceId;

+          let selectedIDs = schedFilters.selectedInstances as Array<any>;

+          let condition = selectedIDs.includes(fetchedID.toString());

+          //console.log(condition);

+          return condition;

+        })

+      }

+

+      resultingData = resultingData.filter(el => {

+        let schedDate = new Date(el.nextRunAt);

+        return schedDate >= this.filters.startDate && schedDate <= this.filters.endDate;

+      })

+

+      for (let index in resultingData) {

+        let checkIfTestBelongsToUserGroup = this.testInstances.findIndex(testInstances => testInstances.id === resultingData[index].data.testSchedule._testInstanceId);

+        if (checkIfTestBelongsToUserGroup >= 0) {

+          if (resultingData[index].nextRunAt) {

+            let d1 = new Date(resultingData[index].nextRunAt);

+            this.scheduledTests.push({

+              id: resultingData[index].data.testSchedule._testInstanceId,

+              name: this.testInstances[checkIfTestBelongsToUserGroup].name,

+              dateExec: d1.toDateString(),

+              timeExec: d1.toLocaleTimeString()

+            })

+          }

+        }

+      }

+      this.scheduleChange.next();

+    });

+    

+

+

+  }

+

+  //getters for the filter objects.

+  // getTDFilters() {

+  //   return this.tdFilters;

+  // }

+

+  // getTIFilters() {

+  //   return this.tiFilters;

+  // }

+

+  // getSchedFilters() {

+  //   return this.scheduleFilters;

+  // }

+

+  calcTime(execution) {

+    var end = new Date(execution.endTime);

+    var start = new Date(execution.startTime);

+    var executionTime = (end.getTime() - start.getTime()) / 1000;

+    return executionTime;

+  }

+

+  //This function takes an execution that was retrieved from the Database and takes the data it needs for the line chart. 

+  populateLineChartData(execution, currentData) {

+    let executionDate = new Date(execution.startTime)

+

+    // Looks to see if the date already has an execution./

+    let indexOfItemFound = currentData.findIndex((element) => {

+

+      return (

+        executionDate.getFullYear() === element.date.getFullYear() &&

+        executionDate.getMonth() === element.date.getMonth() &&

+        executionDate.getDate() === element.date.getDate()

+      )

+    })

+

+    //If the date is not found. Push a new date into the array with a count of one

+    if (currentData[indexOfItemFound] == undefined) {

+      currentData.push({

+        date: new Date(executionDate.getFullYear(), executionDate.getMonth(), executionDate.getDate()),

+        count: 1

+      })

+    // else update the count

+    } else currentData[indexOfItemFound].count += 1;

+  }

+

+

+  //Takes an execution and  pushes the result/count or updates the count. For the Pie Chart

+  populatePieChartData(execution) {

+

+    //Check if result is already present in the array. 

+    var checkIfPresent = this.testDefinitionData.Results.find(Results => Results.Name === execution.testResult);

+   

+    //If not present, add it to TOPSTATs with a default count of 1.    

+    if (!checkIfPresent) {

+      

+      var color;

+      //Set the color for the pie chart.

+      if (execution.testResult == "COMPLETED"){

+        color = "#0D47A1";

+      }else if (execution.testResult == "FAILED")

+        color = "#DD2C00";

+      else if (execution.testResult == "UNKNOWN")

+        color = "#C4CBD4";

+      else if (execution.testResult == "SUCCESS")

+        color = "#42d660";

+      else if (execution.testResult == "success")

+        color = "#42d660";

+      else if (execution.testResult == "STARTED")

+        color = "#29E3E8";

+      else if (execution.testResult == "FAILURE")

+        color = "#FC9100";

+      else if (execution.testResult == "STOPPED")

+        color = "#900C3F";

+      else if (execution.testResult == "TERMINATED")

+        color = "#AC00FC";

+      else if (execution.testResult == "UNAUTHORIZED")

+        color = "#7A6E6E";

+      else if (execution.testResult == "DOES_NOT_EXIST")

+        color = "#000000";

+      else if (execution.testResult == "ERROR")

+        color = "#eb2acd"

+      else if (execution.testResult == "WORKFLOW_ERROR")

+        color = "#194f24"

+      else

+        color = "#000000".replace(/0/g, function () { return (~~(Math.random() * 16)).toString(16); });

+

+      //Push the execution with the count and color.

+      this.testDefinitionData.Results.push({ Name: execution.testResult, Count: 1, color: color });

+    } else {

+

+      //Find index of the testResult and update the count by 1.

+      var position = this.testDefinitionData.Results.findIndex(Results => Results.Name === execution.testResult);

+      this.testDefinitionData.Results[position].Count += 1;

+    }

+  }

+

+  //Takes an execution and pushes result into the barchart. 

+  populateBarChartData(execution) {

+

+    //check if test instance is present in the array. 

+    var checkIfPresent = this.testInstanceData.Executions.find(Instances => Instances.id === execution.historicTestInstance._id);

+

+    //calculates the time it took for the execution/

+    var executionTime = this.calcTime(execution);

+    

+    //If execution is not present, push the test instance with a count of 1.

+    if (!checkIfPresent) {

+      //If not present, add it to testInstanceData with a default count of 1. 

+

+      this.testInstanceData.Executions.push({

+        Name: execution.historicTestInstance.testInstanceName,

+        id: execution.historicTestInstance._id,

+        testResult: execution.testResult,

+        executionTime: executionTime,

+        Count: 1,

+        Average: executionTime

+      });

+    } else {

+      // If Present update count and execution time. 

+      var position = this.testInstanceData.Executions.findIndex(Instances => Instances.id === execution.historicTestInstance._id);

+      this.testInstanceData.Executions[position].Count += 1;

+      this.testInstanceData.Executions[position].executionTime += executionTime;

+      this.testInstanceData.Executions[position].Average = this.testInstanceData.Executions[position].executionTime / this.testInstanceData.Executions[position].Count;

+    }

+

+  }

+

+  //queries data for the scheduled tests. 

+  getScheduledTests(groupId) {

+

+    //Queries a list of test instances by group ID

+    this.testInstanceService.find({

+      groupId: groupId['_id'],

+      $select: ["_id", "testInstanceName", "groupId"],

+      $limit: -1

+    }).subscribe(result => {

+

+      //Iterate through the list and add the test instances to the list. 

+      for (var index in result) {

+        var checkIfPresent = this.testInstances.find(id => id === result[index]._id);

+        if (!checkIfPresent)

+          this.testInstances.push({ id: result[index]._id, name: result[index].testInstanceName });

+      }

+    });

+

+    //Queries all of the scheduled tests. 

+    this.schedService.find({

+

+      $select: ["data.testSchedule._testInstanceId", 'nextRunAt'],

+      $limit: -1,

+      $sort: {

+        startTime: 1

+      },

+

+    }).subscribe(result => {

+

+      this.scheduledTests = [];

+      for (var index in result) {

+

+        //If the scheduled testinstance is owned by the group, push the result. 

+        var checkIfTestBelongsToUserGroup = this.testInstances.findIndex(testInstances => testInstances.id === result[index].data.testSchedule._testInstanceId);

+        if (checkIfTestBelongsToUserGroup >= 0) {

+

+          //If the next run at is valid, the test is scheduled.

+          if (result[index].nextRunAt) {

+            let d1 = new Date(result[index].nextRunAt);

+            this.scheduledTests.push({

+              id: result[index].data.testSchedule._testInstanceId,

+              name: this.testInstances[checkIfTestBelongsToUserGroup].name,

+              dateExec: d1.toDateString(),

+              timeExec: d1.toLocaleTimeString()

+            });

+          }

+        }

+      }

+    });

+  }

+

+  //populate multi line chart

+  populateMultiLineChartData(execution) {

+

+    let executionDate = new Date(execution.startTime)

+    let currentData = this.testInstanceData.Individual_Exec;

+    let count = 1;

+    //find if Instance is already present in the array. 

+    let position = this.testInstanceData.Individual_Exec.findIndex(Instances => Instances.id === execution.historicTestInstance._id);

+

+    //First execution for this instance

+    if (currentData[position] == undefined) {

+      currentData.push({

+        testInstanceName: execution.historicTestInstance.testInstanceName,

+        testDefinitionName: execution.historicTestDefinition.testDefintionName,

+        id: execution.historicTestInstance._id,

+        dateData: [{ date: executionDate, count: count, name: execution.historicTestInstance.testInstanceName }],

+        total: 1

+      })

+      //execution already present

+    } else {

+      //find index of Date

+      let indexOfDate = currentData[position].dateData.findIndex((element) => {

+        return (

+          executionDate.getFullYear() === element.date.getFullYear() &&

+          executionDate.getMonth() === element.date.getMonth() &&

+          executionDate.getDate() === element.date.getDate()

+        )

+      });

+

+      //Check if the exeuction date is valid for this instance. If it is not present, push a new date and count. 

+      if (currentData[position].dateData[indexOfDate] == undefined) {

+        let count = 1;

+        //Push the new Date

+        currentData[position].dateData.push({ date: executionDate, count: count, name: execution.historicTestInstance.testInstanceName, id: execution.historicTestInstance._id});

+        currentData[position].total++;

+      } else {

+        //date is already present

+        currentData[position].dateData[indexOfDate].count++;

+        currentData[position].total++;

+      }

+    }

+  }

+  //Gets the initial data for the default page. 

+  async getDefaultData(group, query?) { 

+    if(!group){

+      return;

+    }

+    

+    this.scheduledTests = [];

+

+    this.startDefaultData.next(group);

+    let groupId = group;

+    //let startDate = moment().subtract(2, 'weeks').toDate();

+    

+    //query sheduled tests

+    //this.getScheduledTests(group);

+

+    if(!query){

+      query = {

+        groupId: groupId['_id'],

+        $select: [

+          'startTime',

+          'endTime',

+          "historicTestDefinition._id",

+          "historicTestDefinition.testName",

+          "historicTestInstance._id",

+          "historicTestInstance.testInstanceName",

+          "testHeadResults.startTime",

+          "testHeadResults.endTime",

+          "testHeadResults.testHeadName",

+          "testHeadResults.testHeadId",

+          "testHeadResults.testHeadGroupId",

+          "testHeadResults.statusCode",

+          'testResult'

+        ],

+        $limit: -1,

+        $sort: {

+          startTime: 1

+        },

+        startTime: {

+          $gte: this.filters.startDate,

+          $lte: this.filters.endDate

+        }

+      }

+    }

+

+    //Query test Executions

+    await new Promise((resolve, reject) => {

+      this.testExecution.find(query).subscribe(result => {

+

+        //reset arrays

+        this.testDefinitionData = {

+          //Executions Array thats made of objects with date, tdName, result

+          "Executions": [],

+          "Results": [],

+        }

+        this.testInstanceData = {

+          "Executions": [],

+          "Individual_Exec": []

+        };

+

+        this.executionList = result as Array<any>;

+        let currentData = this.testDefinitionData.Executions;

+

+        

+

+        //iterate through the results and populate the appropriate arrays. 

+        for (let index in this.executionList) {

+

+          let newItem = this.executionList[index];

+

+          //get default line chart Data

+          this.populateLineChartData(newItem, currentData);

+

+          //get pie chart data. 

+          this.populatePieChartData(newItem);

+

+          //Get BarChart Data

+          //this.populateBarChartData(newItem);

+

+          //get multi line chart data

+          //this.populateMultiLineChartData(newItem);

+        }

+

+        //sort the two arrays descending.

+        this.testInstanceData.Executions.sort((a, b) => b.Count - a.Count);

+        this.testInstanceData.Individual_Exec.sort((a, b) => b.total - a.total);

+        resolve();

+      }, err => {

+        reject(err);

+      });

+    }).then(res => {

+

+      // this.tdFilters = {

+      //   startDate: moment().subtract(2, 'weeks').toDate(),

+      //   endDate: moment().toDate(),

+      //   selected: [],

+      // };

+      // this.tiFilters = {

+      //   startDate: moment().subtract(2, 'weeks').toDate(),

+      //   endDate: moment().toDate(),

+      //   selectedTDs: [],

+      //   selectedTIs: [],

+      //   multiLineLimit: 5,

+      // }

+      this.finishedDefaultData.next(res);

+    })

+  }

+

+

+}

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
index 0000000..b44afa2
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.pug
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..70410d8
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestDefinitionExecutionsBarChartComponent } from './test-definition-executions-bar-chart.component';

+

+describe('TestDefinitionExecutionsBarChartComponent', () => {

+  let component: TestDefinitionExecutionsBarChartComponent;

+  let fixture: ComponentFixture<TestDefinitionExecutionsBarChartComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestDefinitionExecutionsBarChartComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestDefinitionExecutionsBarChartComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..866de65
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component.ts
@@ -0,0 +1,198 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewChild, ElementRef, Input, OnDestroy } from '@angular/core';

+import { StatsService } from '../stats.service';

+import * as am4core from "@amcharts/amcharts4/core";

+import * as am4charts from "@amcharts/amcharts4/charts";

+import { e } from '@angular/core/src/render3';

+import * as moment from 'moment';

+import { Router } from '@angular/router';

+import { Subscription } from 'rxjs';

+

+@Component({

+  selector: 'app-test-definition-executions-bar-chart',

+  templateUrl: './test-definition-executions-bar-chart.component.pug',

+  styleUrls: ['./test-definition-executions-bar-chart.component.scss']

+})

+export class TestDefinitionExecutionsBarChartComponent implements OnInit, OnDestroy {

+

+  private toDestroy: Array<Subscription> = [];

+

+  @ViewChild('chart') chartElement: ElementRef;

+  @Input() height: string;

+

+  public chart: am4charts.XYChart;

+  public testInstanceData;

+  public loadingIndicator;

+

+  constructor(private stats: StatsService, private router: Router) {}

+

+

+  ngOnInit() {

+    this.renderChart();

+

+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onTIExecutionChangeStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {

+      this.setChartData();

+    }));

+

+    this.toDestroy.push(this.stats.onTIExecutionChangeFinished().subscribe(res => {

+      this.setChartData()

+    }));

+

+  }

+

+  ngOnDestroy() {

+    this.toDestroy.forEach(e => e.unsubscribe());

+    this.chart.dispose();

+  }

+

+  showLoadingIndicator() {

+    if(!this.loadingIndicator){

+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);

+      this.loadingIndicator.background.fill = am4core.color("#fff");

+      this.loadingIndicator.background.fillOpacity = 0.8;

+      this.loadingIndicator.width = am4core.percent(100);

+      this.loadingIndicator.height = am4core.percent(100);

+

+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);

+      indicatorLabel.text = "Loading..";

+      indicatorLabel.align = "center";

+      indicatorLabel.valign = "middle";

+      indicatorLabel.fontSize = 18;

+      indicatorLabel.fontWeight= "bold";

+      indicatorLabel.dy = 50;

+

+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);

+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";

+      loadingImage.href = "/assets/images/equalizer.gif";

+      //loadingImage.dataSource = "/loading-pies.svg"

+      loadingImage.align = "center";

+      loadingImage.valign = "middle";

+      loadingImage.horizontalCenter = "middle";

+      loadingImage.verticalCenter = "middle";

+      loadingImage.scale = 3.0;

+    }else{

+      this.loadingIndicator.show();

+    }

+

+  }

+

+  hideLoadingIndicator() {

+    this.loadingIndicator.hide();

+  }

+

+  setChartData() {

+

+    let data = [];

+    this.stats.executionList.forEach((execution, i) => {

+      let index = data.findIndex(e => e.id === execution.historicTestDefinition._id);

+      let executionTime = moment(execution.endTime).diff(moment(execution.startTime), 'seconds');

+      if(index == -1){

+        data.push({

+          id: execution.historicTestDefinition._id,

+          name: execution.historicTestDefinition.testName,

+          testResult: execution.testResult,

+          executionTime: executionTime,

+          count: 1,

+          average: executionTime

+        })

+      }else{

+        data[index].count += 1;

+        data[index].executionTime += executionTime;

+        data[index].average = (data[index].executionTime / data[index].count);

+      }

+    });

+    data.sort((a, b) => b.count - a.count);

+    this.chart.data = data;

+    

+

+    // Displays the average time for each bar. 

+    // If there is no time recorded for the Test Instance, display No Time Recorded.

+    let series = this.chart.series.values[0] as am4charts.ColumnSeries;

+    

+    series.columns.template.adapter.add("tooltipText", (text, target) => {

+      if (target.dataItem) {

+        if (this.chart.data[target.dataItem.index].average > 0) {

+          return this.chart.data[target.dataItem.index].count.toString() + " Executions \n Avg Time: " + this.chart.data[target.dataItem.index].average.toFixed(2).toString() + " seconds";

+        } else

+          return this.chart.data[target.dataItem.index].count.toString() + " Executions \n No Time Recorded";

+      }

+    });

+    series.columns.template.adapter.add("fill", (fill, target) => this.chart.colors.getIndex(target.dataItem.index));

+    

+    

+    series.columns.template.events.on("doublehit", (click) => {

+      this.router.navigate(['/test-definitions', click.target.dataItem.dataContext['id']]);

+    });

+    this.chart.appear();

+    this.hideLoadingIndicator();

+    

+  }

+

+  renderChart() {

+    this.chart = am4core.create(this.chartElement.nativeElement, am4charts.XYChart);

+    this.chart.cursor = new am4charts.XYCursor();

+    this.showLoadingIndicator();

+

+    this.chart.responsive.enabled = true;

+

+    // Create axes

+    var categoryAxis = this.chart.yAxes.push(new am4charts.CategoryAxis());

+    categoryAxis.dataFields.category = "name";

+    categoryAxis.numberFormatter.numberFormat = "#";

+    categoryAxis.renderer.inversed = true;

+    categoryAxis.renderer.minGridDistance = 5;

+    categoryAxis.title.fontSize = "10px";

+

+    var valueAxis = this.chart.xAxes.push(new am4charts.ValueAxis());

+    valueAxis.renderer.minWidth = 10;

+

+    // Create series

+    var series = this.chart.series.push(new am4charts.ColumnSeries());

+    series.dataFields.valueX = "count";

+    series.dataFields.categoryY = "name";

+    series.columns.template.tooltipText = " ";

+

+    let label = categoryAxis.renderer.labels.template;

+    label.truncate = true;

+    label.maxWidth = 130;

+    label.fontSize = 13;

+

+    //Scrollbar on the right. 

+    let scrollBarY = new am4charts.XYChartScrollbar();

+    //scrollBarY.series.push(series);

+    this.chart.scrollbarY = scrollBarY;

+    this.chart.scrollbarY.contentHeight = 100;

+    this.chart.scrollbarY.minWidth = 20;

+    this.chart.scrollbarY.thumb.minWidth = 20;

+

+    //set initial Scrollbar Zoom to the Top 6 Instances. 

+    this.chart.events.on("appeared", () => {

+      

+      categoryAxis.zoomToIndexes(0, 6, false, true);

+    });

+   }

+

+}

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
index 0000000..b44afa2
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.pug
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..351d650
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestHeadExecutionBarChartComponent } from './test-head-execution-bar-chart.component';

+

+describe('TestHeadExecutionBarChartComponent', () => {

+  let component: TestHeadExecutionBarChartComponent;

+  let fixture: ComponentFixture<TestHeadExecutionBarChartComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestHeadExecutionBarChartComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestHeadExecutionBarChartComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..9b17262
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component.ts
@@ -0,0 +1,236 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewChild, ElementRef, Input, OnDestroy } from '@angular/core';

+import * as moment from 'moment';

+import { Subscription } from 'rxjs';

+import { StatsService } from '../stats.service';

+import { Router } from '@angular/router';

+import * as am4core from "@amcharts/amcharts4/core";

+import * as am4charts from "@amcharts/amcharts4/charts";

+@Component({

+  selector: 'app-test-head-execution-bar-chart',

+  templateUrl: './test-head-execution-bar-chart.component.pug',

+  styleUrls: ['./test-head-execution-bar-chart.component.scss']

+})

+export class TestHeadExecutionBarChartComponent implements OnInit, OnDestroy {

+

+  private toDestroy: Array<Subscription> = [];

+

+  @ViewChild('chart') chartElement: ElementRef;

+  @Input() height: string;

+

+  public chart: am4charts.XYChart;

+  public testInstanceData;

+  public loadingIndicator;

+

+  constructor(private stats: StatsService, private router: Router) {}

+

+

+  ngOnInit() {

+    

+    this.renderChart();

+

+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onTIExecutionChangeStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {

+      this.setChartData();

+    }));

+

+    this.toDestroy.push(this.stats.onTIExecutionChangeFinished().subscribe(res => {

+      this.setChartData()

+    }));

+

+  }

+

+  ngOnDestroy() {

+    this.toDestroy.forEach(e => e.unsubscribe());

+    this.chart.dispose();

+  }

+

+  showLoadingIndicator() {

+    if(!this.loadingIndicator){

+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);

+      this.loadingIndicator.background.fill = am4core.color("#fff");

+      this.loadingIndicator.background.fillOpacity = 0.8;

+      this.loadingIndicator.width = am4core.percent(100);

+      this.loadingIndicator.height = am4core.percent(100);

+

+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);

+      indicatorLabel.text = "Loading..";

+      indicatorLabel.align = "center";

+      indicatorLabel.valign = "middle";

+      indicatorLabel.fontSize = 18;

+      indicatorLabel.fontWeight= "bold";

+      indicatorLabel.dy = 50;

+

+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);

+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";

+      loadingImage.href = "/assets/images/equalizer.gif";

+      //loadingImage.dataSource = "/loading-pies.svg"

+      loadingImage.align = "center";

+      loadingImage.valign = "middle";

+      loadingImage.horizontalCenter = "middle";

+      loadingImage.verticalCenter = "middle";

+      loadingImage.scale = 3.0;

+    }else{

+      this.loadingIndicator.show();

+    }

+

+  }

+

+  hideLoadingIndicator() {

+    this.loadingIndicator.hide();

+  }

+

+  incrementStatus(data, code){

+    

+    if(code >= 200 && code < 300){

+      data["200"]++;

+    }else if(code >= 300 && code < 400){

+      data["300"]++;

+    }else if(code >= 400 && code < 500){

+      data["400"]++;

+    }else if(code >= 500 && code < 600){

+      data["500"]++;

+    }else{

+      

+      data["other"]++;

+    }

+  }

+

+  setChartData() {

+

+    let data = [];

+    this.stats.executionList.forEach((execution, i) => {

+      execution.testHeadResults.forEach((result, val) => {

+        let index = data.findIndex(e => e.id === result.testHeadId);

+        let executionTime = moment(result.endTime).diff(moment(result.startTime), 'seconds');

+        if(index == -1){

+          let toPush = {

+            id: result.testHeadId,

+            name: result.testHeadName,

+            executionTime: executionTime,

+            count: 1,

+            average: executionTime,

+            "200": 0,

+            "300": 0,

+            "400": 0,

+            "500": 0,

+            "other": 0

+          }

+          this.incrementStatus(toPush, result.statusCode);

+          data.push(toPush);

+        }else{

+          this.incrementStatus(data[index], result.statusCode);

+          data[index].count += 1;

+          data[index].executionTime += executionTime;

+          data[index].average = (data[index].executionTime / data[index].count);

+        }

+      });

+    });

+    data.sort((a, b) => b.count - a.count);

+    this.chart.data = data;

+

+    // Displays the average time for each bar. 

+    // If there is no time recorded for the Test Instance, display No Time Recorded.

+    let series = this.chart.series.values as Array<am4charts.ColumnSeries>;

+    

+    // series.columns.template.adapter.add("tooltipText", (text, target) => {

+    //   if (target.dataItem) {

+    //     if (this.chart.data[target.dataItem.index].average > 0) {

+    //       return this.chart.data[target.dataItem.index].count.toString() + " Executions \n Avg Time: " + this.chart.data[target.dataItem.index].average.toFixed(2).toString() + " seconds";

+    //     } else

+    //       return this.chart.data[target.dataItem.index].count.toString() + " Executions \n No Time Recorded";

+    //   }

+    // });

+

+    series.forEach(elem => {

+      // elem.columns.template.adapter.add("fill", (fill, target) => this.chart.colors.getIndex(target.dataItem.index));

+    

+    

+      elem.columns.template.events.on("doublehit", (click) => {

+        this.router.navigate(['/test-heads', click.target.dataItem.dataContext['id']]);

+      });

+    })

+    

+    this.chart.appear();

+    this.hideLoadingIndicator();

+  }

+

+  renderChart() {

+    this.chart = am4core.create(this.chartElement.nativeElement, am4charts.XYChart);

+

+    this.showLoadingIndicator();

+

+    this.chart.responsive.enabled = true;

+

+    // Create axes

+    var categoryAxis = this.chart.yAxes.push(new am4charts.CategoryAxis());

+    categoryAxis.dataFields.category = "name";

+    categoryAxis.numberFormatter.numberFormat = "#";

+    categoryAxis.renderer.inversed = true;

+    categoryAxis.renderer.minGridDistance = 5;

+    categoryAxis.title.fontSize = "10px";

+

+    var valueAxis = this.chart.xAxes.push(new am4charts.ValueAxis());

+    valueAxis.renderer.minWidth = 10;

+

+    this.createSeries("200", "200s")

+    this.createSeries("300", "300s")

+    this.createSeries("400", "400s")

+    this.createSeries("500", "500s")

+    this.createSeries("other", "Other")

+

+    this.chart.legend = new am4charts.Legend();

+    this.chart.legend.scale = .7;

+    this.chart.legend.width = am4core.percent(150);

+

+    let label = categoryAxis.renderer.labels.template;

+    label.truncate = true;

+    label.maxWidth = 130;

+    label.fontSize = 13;

+

+    //Scrollbar on the right. 

+    let scrollBarY = new am4charts.XYChartScrollbar();

+    //scrollBarY.series.push(series);

+    this.chart.scrollbarY = scrollBarY;

+    this.chart.scrollbarY.contentHeight = 100;

+    this.chart.scrollbarY.minWidth = 20;

+    this.chart.scrollbarY.thumb.minWidth = 20;

+

+    //set initial Scrollbar Zoom to the Top 6 Instances. 

+    this.chart.events.on("appeared", () => {

+      categoryAxis.zoomToIndexes(0, 6, false, true);

+    });

+   }

+

+  createSeries(field, name){

+    // Create series

+    var series = this.chart.series.push(new am4charts.ColumnSeries());

+    series.dataFields.valueX = field;

+    series.dataFields.categoryY = "name";

+    series.stacked = true;

+    series.name = name;

+  }

+

+}

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
index 0000000..b44afa2
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.pug
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..af1de53
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestHeadExecutionsLineChartComponent } from './test-head-executions-line-chart.component';

+

+describe('TestHeadExecutionsLineChartComponent', () => {

+  let component: TestHeadExecutionsLineChartComponent;

+  let fixture: ComponentFixture<TestHeadExecutionsLineChartComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestHeadExecutionsLineChartComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestHeadExecutionsLineChartComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..a214c87
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component.ts
@@ -0,0 +1,219 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, ViewChild, ElementRef, OnDestroy } from '@angular/core';

+import { StatsService } from '../stats.service';

+import * as am4core from "@amcharts/amcharts4/core";

+import * as am4charts from "@amcharts/amcharts4/charts";

+import { _ } from 'ag-grid-community';

+import * as moment from 'moment';

+import { Subscription } from 'rxjs';

+import { GroupService } from 'app/shared/services/group.service';

+

+@Component({

+  selector: 'app-test-head-executions-line-chart',

+  templateUrl: './test-head-executions-line-chart.component.pug',

+  styleUrls: ['./test-head-executions-line-chart.component.scss']

+})

+export class TestHeadExecutionsLineChartComponent implements OnInit {

+

+  private toDestroy: Array<Subscription> = [];

+

+  @ViewChild('chart') chartElement: ElementRef;

+  @Input() height: string;

+

+  //public testDefinitionName = "Hello";

+  private chart: am4charts.XYChart;

+  private loadingIndicator;

+

+  constructor(private stats: StatsService, private _groups: GroupService) {

+  }

+

+  ngOnInit() {

+    this.renderChart();

+

+    this.toDestroy.push(this.stats.onTDExecutionChangeStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {

+      this.setChartData();

+    }));

+

+    this.toDestroy.push(this.stats.onTDExecutionChangeFinished().subscribe(res => {

+      this.setChartData();

+    }));

+

+  }

+

+  ngOnDestroy() {

+    //destory chart

+    this.toDestroy.forEach(e => e.unsubscribe());

+    this.chart.dispose();

+  }

+

+  //Sets count to 0 for any dates that dont have data

+  setupPoints(rawData) {

+    let formattedData = [];

+    let dayRange = moment(this.stats.filters.endDate).add(1, 'days').diff(moment(this.stats.filters.startDate), 'days');

+    for (let i = 0; i < dayRange; i++) {

+      //find date in raw data

+      let d = rawData.find(e => moment(e.date).isSame(moment(this.stats.filters.startDate).add(i, 'days'), 'day'));

+      let myTestHeads = 0;

+      let otherTestHeads = 0;

+      if (d) {

+        myTestHeads = d.myTestHeads || 0;

+        otherTestHeads = d.otherTestHeads || 0;

+      }

+      formattedData.push({

+        date: moment(this.stats.filters.startDate).startOf('day').add(i, 'days').toDate(),

+        myTestHeads: myTestHeads,

+        otherTestHeads: otherTestHeads

+      })

+    }

+

+    return formattedData;

+  }

+

+  showLoadingIndicator() {

+

+    //this.height = "380px";

+    if (!this.loadingIndicator) {

+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);

+      this.loadingIndicator.background.fill = am4core.color("#fff");

+      this.loadingIndicator.background.fillOpacity = 0.8;

+      this.loadingIndicator.width = am4core.percent(100);

+      this.loadingIndicator.height = am4core.percent(100);

+

+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);

+      indicatorLabel.text = "Loading..";

+      indicatorLabel.align = "center";

+      indicatorLabel.valign = "middle";

+      indicatorLabel.fontSize = 18;

+      indicatorLabel.fontWeight = "bold";

+      indicatorLabel.dy = 50;

+

+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);

+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";

+      loadingImage.href = "/assets/images/equalizer.gif";

+      //loadingImage.dataSource = "/loading-pies.svg"

+      loadingImage.align = "center";

+      loadingImage.valign = "middle";

+      loadingImage.horizontalCenter = "middle";

+      loadingImage.verticalCenter = "middle";

+      loadingImage.scale = 3.0;

+    } else {

+      this.loadingIndicator.show();

+    }

+  }

+

+  hideLoadingIndicator() {

+    this.loadingIndicator.hide();

+  }

+

+  async setChartData() {

+    let data = [];

+

+    this.stats.executionList.forEach((e, i) => {

+      if (e.testHeadResults && e.testHeadResults.length > 0) {

+

+        e.testHeadResults.forEach((result, index) => {

+

+          let isMyTestHead = result.testHeadGroupId == this._groups.getGroup()['_id'];

+

+

+

+          let dataIndex = data.findIndex(d => moment(d.date).isSame(result.startTime, 'day'));

+

+          if (dataIndex == -1) {

+            dataIndex = data.push({ date: moment(result.startTime).toDate() }) - 1;

+          }

+

+          if (isMyTestHead) {

+            if (data[dataIndex]['myTestHeads']) {

+              data[dataIndex]['myTestHeads'] += 1;

+            } else {

+              data[dataIndex]['myTestHeads'] = 1;

+            }

+          }else{

+            if (data[dataIndex]['otherTestHeads']) {

+              data[dataIndex]['otherTestHeads'] += 1;

+            } else {

+              data[dataIndex]['otherTestHeads'] = 1;

+            }

+          }

+

+        })

+      }

+    });

+

+    

+

+    this.chart.data = this.setupPoints(data);

+

+    this.hideLoadingIndicator();

+  }

+

+  renderChart() {

+

+    if (this.chart) {

+      this.chart.dispose();

+    }

+    this.chart = am4core.create(this.chartElement.nativeElement, am4charts.XYChart);

+    this.chart.preloader.disabled = true;

+    this.showLoadingIndicator();

+

+    let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());

+    dateAxis.fontSize = "10px";

+

+    let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());

+    valueAxis.title.text = "Executions";

+    valueAxis.title.fontSize = "10px";

+

+    let series = this.chart.series.push(new am4charts.LineSeries());

+    series.name = "My Group's VTHs"

+    series.dataFields.dateX = "date";

+    series.dataFields.valueY = "myTestHeads";

+    series.strokeWidth = 3;

+

+    series.fillOpacity = .5;  

+    // series.tensionX = 0.8;

+    series.sequencedInterpolation = false;

+    series.tooltipText = "{valueY.value}";

+

+    let seriesOthers = this.chart.series.push(new am4charts.LineSeries());

+    seriesOthers.name = "Other VTHs";

+    seriesOthers.dataFields.dateX = "date";

+    seriesOthers.dataFields.valueY = "otherTestHeads";

+    seriesOthers.strokeWidth = 3;

+    seriesOthers.tooltipText = "{valueY.value}";

+

+    this.chart.cursor = new am4charts.XYCursor();

+

+    this.chart.responsive.enabled = true;

+

+    this.chart.legend = new am4charts.Legend();

+    this.chart.legend.labels.template.text = "[bold {color}]{name}";

+    this.chart.legend.scale = .8;

+  }

+

+

+

+}

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
index 0000000..29b38ef
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/control-panel/control-panel-routing.module.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { ControlPanelComponent } from './control-panel.component';

+

+const routes: Routes = [

+  { path: '', component: ControlPanelComponent }

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class ControlPanelRoutingModule { }

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
index 0000000..56b688f
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/control-panel/control-panel.component.pug
@@ -0,0 +1,270 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition], style="margin-top: -15px")

+    .row.fullWidth.mb-4(style="position:relative")

+        div.pull-tab

+            div.arrow

+        mat-card#canvas-card(style="width: 100%; resize:vertical; overflow: hidden")

+            mat-card-content#canvas(style="height: 100%")

+                div(style="position: absolute; bottom: 5px; left: 10px")

+                    div.small.text-muted Test Definition: {{ testResult?.historicTestDefinition?.testName }}

+                    div.small.text-muted Version: {{ testResult?.historicTestDefinition?.bpmnInstances[0]?.version }}

+                div(style="position: absolute; bottom: 5px; right: 25px")

+                    div.small.text-muted(*ngIf="processState")

+                        | Status: {{ processState }}

+    .row.mb-4

+        .col

+            .pull-left

+                h3 {{ testResult?.historicTestInstance?.testInstanceName }} Execution Log

+                div {{ testResult?.historicTestInstance?.testInstanceDescription }}

+            button.mr-2.pull-right(color="accent", mat-raised-button, (click)="refreshAllData()") Refresh

+                i.fa.fa-2x.fa-fw.fa-refresh.fast-spin(*ngIf="spin")

+            button.mr-2.pull-right(*ngIf="processState && processState == 'Running'", color="warn", mat-raised-button, (click)="cancelExecution()") Cancel Execution

+    .row.mb-4

+        .col-12

+            mat-tab-group([selectedIndex]="0", dinamicHeight, color="accent", backgroundColor="primary")

+                mat-tab(label="Overview")

+                    .col-12.mt-2

+                        .row.mb-4(style="text-align:center")

+                            .col

+                                b Test Result:

+                                div {{ testResult?.testResult }}

+                            .col

+                                b Start Time:

+                                div {{ testResult?.startTime }}

+                            .col

+                                b End Time:

+                                div {{ testResult?.endTime }}

+                            .col

+                                b Total Time:

+                                div {{ testResult?.totalTime }}

+                            .col

+                                b Date Executed:

+                                div {{ testResult?.date }}

+                            .col

+                                b Executed By:

+                                div {{ testResult?.historicEmail }}

+                        hr

+                        .row.mb-4(*ngIf="testResult?.testResultMessage")

+                            .col-12

+                                h5 Test Result Message

+                                | {{ testResult?.testResultMessage }}

+                        .row

+                            .col-12

+                                h5 Test Details

+                                //- table(datatable, [dtOptions]="dtOptions", class="row-border hover")

+                                //-     thead

+                                //-         tr

+                                //-             th(*ngFor="let column of columns") {{ column.title }}

+                                //-     tbody

+                                //-         tr(*ngFor="let key of data")

+                                //-             td(*ngFor="let column of columns") {{ key[column.data] }}

+                                ngx-json-viewer(*ngIf="testResult && testResult.testDetails != {}", [json]="testResult.testDetails", style="font-size: 1.3em")

+                                //- div(*ngIf="testResult && testResult.testDetails != {}", [innerHTML]="json2html(testResult.testDetails)")

+                                //- div(*ngIf="testResult") {{ json2html(testResult.testDetails) != '' ? '' : 'No test details were set during execution.' }}

+

+                mat-tab(*ngIf="testResult?.testInstanceResults.length > 0", label="Test Instance Results")

+                    .col-12.mt-2

+                        .row

+                            .col-3

+                                h5 Test Instances

+                                mat-list

+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(instanceDataSource); last as last; first as first", (click)="selectTestInstance(key)")

+                                        h5 {{ instanceDataSource[key][0].historicTestInstance.testInstanceName }}

+                                        mat-icon([hidden]="selectedTestInstance != key") keyboard_arrow_left

+                                        mat-divider([inset]="true", *ngIf="!last")

+                                mat-divider(vertical)

+

+                            .col-9

+                                mat-accordion([multi]="true")

+                                    mat-expansion-panel(*ngFor="let element of instanceDataSource[selectedTestInstance]; let i = index;")

+                                        mat-expansion-panel-header()

+                                            mat-panel-title {{ '#' + (i + 1)}}

+                                            mat-panel-description

+                                                | {{ element.testResult }}

+                                        .col-12

+                                            .row(style="text-align:center")

+                                                .col

+                                                    b Test Result:

+                                                    div {{ element.testResult }}

+                                                .col

+                                                    b Start Time:

+                                                    div {{ element.startTime }}

+                                                .col

+                                                    b End Time:

+                                                    div {{ element.endTime }}

+                                                .col

+                                                    b Total Time:

+                                                    div {{ element.totalTime }}

+                                                .col

+                                                    b Date Executed:

+                                                    div {{ element.date }}

+                                            hr

+                                            div(style="float:right")

+                                                button(mat-raised-button, color="primary", [routerLink]="['/control-panel']", [queryParams]="{id: element._id}") Full Execution

+                                            .row.mb-4(*ngIf="element.testResultMessage")

+                                                .col-12

+                                                    h5 Test Result Message

+                                                    | {{ element.testResultMessage }}

+                                            .row

+                                                .col-12

+                                                    h5 Test Details

+                                                    ngx-json-viewer(*ngIf="element && element.testDetails != {}", [json]="element.testDetails")

+                                                    //- div(*ngIf="element && element.testDetails != {}", [innerHTML]="json2html(element.testDetails)")

+                                                    //- div(*ngIf="testResult") {{ json2html(element.testDetails) != '' ? '' : 'No test details were set during execution.' }}

+

+                mat-tab(*ngIf="testResult?.testHeadResults.length > 0", label="Test Head Results")

+                    .col-12.mt-2

+                        .row

+                            .col-3

+                                h5 Test Heads

+                                mat-list

+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(dataSource); last as last; first as first", (click)="selectTestHead(key)")

+                                        div

+                                            h5 {{ dataSource[key][0].testHeadName }}

+                                            div.small(style="margin-top:-11px;") {{ dataSource[key][0].bpmnVthTaskId }}

+                                        mat-icon([hidden]="selectedTestHead != key") keyboard_arrow_left

+                                        mat-divider([inset]="true", *ngIf="!last")

+                                mat-divider(vertical)

+

+                            .col-9

+                                div(*ngFor="let testHead of testHeads")

+                                    mat-accordion([multi]="true", *ngIf="selectedTestHead == testHead.testHeadId + testHead.bpmnVthTaskId")

+                                        mat-expansion-panel(*ngFor="let element of dataSource[testHead.testHeadId + testHead.bpmnVthTaskId]; let i = index;")

+                                            mat-expansion-panel-header()

+                                                mat-panel-title {{ '#' + (i + 1)}}

+                                                mat-panel-description

+                                                    | {{ element.totalTime }}

+                                            ngx-json-viewer([json]="element.testHeadRequestResponse")

+                                            app-robot-report(*ngIf="element.testHeadResponse", [response]="element.testHeadResponse")

+

+                    //- (*ngFor="let testHead of testHeads")

+                    //-     mat-card

+                    //-         mat-card-header.COMPLETED-dash

+                    //-             mat-card-title.pull-left {{testHead.testHeadName}} Results

+                    //-             .pull-right {{testHead.bpmnVthTaskId}}

+                    //-         mat-card-content

+                    //-             table(mat-table, multiTemplateDataRows, [dataSource]="dataSource[testHead.testHeadId + testHead.bpmnVthTaskId]")

+

+                    //-                 ng-container(matColumnDef="startTime")

+                    //-                     th(mat-header-cell, *matHeaderCellDef) Start Time

+                    //-                     td(mat-cell, *matCellDef="let element") {{ element.startTime}}

+

+                    //-                 ng-container(matColumnDef="endTime")

+                    //-                     th(mat-header-cell, *matHeaderCellDef) End Time

+                    //-                     td(mat-cell, *matCellDef="let element") {{ element.endTime }}

+

+                    //-                 ng-container(matColumnDef="totalTime")

+                    //-                     th(mat-header-cell, *matHeaderCellDef) Total Time

+                    //-                     td(mat-cell, *matCellDef="let element") {{ element.totalTime }}

+

+                    //-                 ng-container(matColumnDef="expandedDetail")

+                    //-                     td(mat-cell, style="padding:0px", *matCellDef="let element; let i = dataIndex", [attr.colspan]="displayedColumns.length")

+                    //-                         div([@detailExpand]="(testHead.testHeadId + testHead.bpmnVthTaskId + i) == expandedElement ? 'expanded' : 'collapsed'")

+                    //-                             codemirror([config]="codeConfig", [value]='element.testHeadResponse', name="testHeadResult")

+                    //-                             app-robot-report(*ngIf="element.testHeadResponse", [response]="element.testHeadResponse")

+

+                    //-                 tr(mat-header-row, *matHeaderRowDef="displayedColumns")

+                    //-                 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)")

+                    //-                 tr.example-detail-row(mat-row, *matRowDef="let row; columns: ['expandedDetail']")

+

+                mat-tab(label="Task Log", *ngIf="taskLog != ''")

+                    .col-12.mt-2

+                        h5 Task Logs

+                        div {{ taskLog }}

+

+                mat-tab(label="Test Parameters", *ngIf="testResult?.historicTestInstance")

+                    .col-12.mt-2

+                        h5 Test Data

+                        ngx-json-viewer([json]="testResult.historicTestInstance.testData", [expanded]="false", style="font-size: 1.3em")

+                        //- div([innerHTML]="json2html(testResult.historicTestInstance.testData, 1)")

+                        //- | {{ testResult.historicTestInstance.testData ? '' : 'No test data set.'}}

+                        h5.mt-1 Test Head Input

+                        ngx-json-viewer([json]="testResult.historicTestInstance.vthInput", [expanded]="false", style="font-size: 1.3em")

+                        //- div([innerHTML]="json2html(testResult.historicTestInstance.vthInput, 1)")

+                        //- | {{ testResult.historicTestInstance.vthInput ? '' : 'No test head input set.'}}

+

+

+                mat-tab(*ngIf="executionJobLogDataSource != undefined", label="Execution Job Log")

+                    .col-12.mt-2

+                        .row

+                            .col-3

+                                h5 Execution Job Log

+                                mat-list

+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(executionJobLogDataSource); last as last; first as first", (click)="selectExecutionJobLog(key)")

+                                        h5 {{ executionJobLogDataSource[key][0].activityId }}

+                                        mat-icon([hidden]="selectedExecutionJobLog != key") keyboard_arrow_left

+                                        mat-divider([inset]="true", *ngIf="!last")

+                                mat-divider(vertical)

+

+                            .col-9

+                                mat-accordion([multi]="true")

+                                    mat-expansion-panel(*ngFor="let element of executionJobLogDataSource[selectedExecutionJobLog]; let i = index;")

+                                        mat-expansion-panel-header()

+                                            mat-panel-title {{ '#' + (i + 1)}}

+                                            mat-panel-description

+                                                | {{ element.id }}

+                                        ngx-json-viewer([json]="element")

+                                        //- div([innerHTML]="json2html(element)")

+                                        //-     | {{ element ? '' : 'No job log' }}

+

+                mat-tab(*ngIf="executionExternalTaskLogDataSource != undefined", label="Execution External Task Log")

+                    .col-12.mt-2

+                        .row

+                            .col-3

+                                h5 Execution External Task Log

+                                mat-list

+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(executionExternalTaskLogDataSource); last as last; first as first", (click)="selectExecutionExternalTaskLog(key)")

+                                        h5 {{ executionExternalTaskLogDataSource[key][0].activityId }}

+                                        mat-icon([hidden]="selectedExecutionExternalTaskLog != key") keyboard_arrow_left

+                                        mat-divider([inset]="true", *ngIf="!last")

+                                mat-divider(vertical)

+

+                            .col-9

+                                mat-accordion([multi]="true")

+                                    mat-expansion-panel(*ngFor="let element of executionExternalTaskLogDataSource[selectedExecutionExternalTaskLog]; let i = index;")

+                                        mat-expansion-panel-header()

+                                            mat-panel-title {{ '#' + (i + 1)}}

+                                            mat-panel-description

+                                                | {{ element.id }}

+                                        ngx-json-viewer([json]="element")

+                                        //- div([innerHTML]="json2html(element)")

+                                        //-     | {{ element ? '' : 'No external task log' }}

+

+

+                mat-tab(*ngIf="executionVariablesDataSource != undefined", label="Execution Variables")

+                    .col-12.mt-2

+                        .row

+                            .col-3

+                                h5 Execution Variables

+                                mat-list

+                                    mat-list-item(style="cursor: pointer", *ngFor="let key of objectKeys(executionVariablesDataSource); last as last; first as first", (click)="selectExecutionVariable(key)")

+                                        h5 {{ executionVariablesDataSource[key][0].variableName }}

+                                        mat-icon([hidden]="selectedExecutionVariable != key") keyboard_arrow_left

+                                        mat-divider([inset]="true", *ngIf="!last")

+                                mat-divider(vertical)

+

+                            .col-9

+                                mat-accordion([multi]="true")

+                                    mat-expansion-panel(*ngFor="let element of executionVariablesDataSource[selectedExecutionVariable]; let i = index;")

+                                        mat-expansion-panel-header()

+                                            mat-panel-title {{ '#' + (i + 1)}}

+                                            mat-panel-description

+                                                | {{ element.id }}

+                                        ngx-json-viewer([json]="element")

+                                        //- div([innerHTML]="json2html(element)")

+                                        //-     | {{ element ? '' : 'No variables' }}

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
index 0000000..eae8b37
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/control-panel/control-panel.component.scss
@@ -0,0 +1,132 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.COMPLETED-dash {

+    background-color: #0d47a1;

+    color: white;

+}

+

+.SUCCESS-dash {

+    background-color: #199700;

+    color: white;

+}

+

+.FAILURE-dash {

+    background-color: #dd2c00 !important;

+    color: white;

+}

+

+.fast-spin {

+  -webkit-animation: fa-spin 1s infinite linear;

+  animation: fa-spin 1s infinite linear;

+}

+

+.STOPPED-dash {

+    background-color: #ff9100;

+    color: white;

+}

+

+.UNAUTHORIZED-dash {

+    background-color: #000000;

+    color: white;

+}

+

+.UNKNOWN-dash {

+    background-color: White;

+}

+

+table {

+    width: 100%;

+    table-layout:fixed; 

+  }

+  

+  tr.example-detail-row {

+    height: 0;

+  }

+  

+  tr.example-element-row:not(.example-expanded-row):hover {

+    background: #f5f5f5;

+    cursor: pointer;

+  }

+  

+  tr.example-element-row:not(.example-expanded-row):active {

+    background: #efefef;

+    cursor: pointer;

+  }

+  

+  .example-element-row td {

+    border-bottom-width: 0;

+  }

+  

+  .example-element-detail {

+    //overflow: hidden;

+    //display: flex;

+  }

+  

+  .example-element-diagram {

+    min-width: 80px;

+    border: 2px solid black;

+    padding: 8px;

+    font-weight: lighter;

+    margin: 8px 0;

+    height: 104px;

+  }

+  

+  .example-element-symbol {

+    font-weight: bold;

+    font-size: 40px;

+    line-height: normal;

+  }

+  

+  .example-element-description {

+    padding: 16px;

+  }

+  

+  .example-element-description-attribution {

+    opacity: 0.5;

+  }

+

+

+.active-testHead {

+  background-color: #f8f9fa;

+}

+

+.pull-tab {

+  height: 0px;

+  width: 0px;

+  border-top: 20px solid #007bff;

+  border-left: 20px solid transparent;

+  border-right: 20px solid transparent;

+  -webkit-transform: rotate(-45deg);

+  position: absolute;

+  bottom: 0px;

+  right: -15px;

+  pointer-events: none;

+  z-index: 1;

+  margin-bottom: -4px;

+}

+

+.arrow {

+  border: solid black;

+  border-width: 0 3px 3px 0;

+  display: inline-block;

+  padding: 3px;

+  position: absolute;

+  transform: rotate(45deg);

+  -webkit-transform: rotate(45deg);

+  right: -2px;

+  bottom: 11px;

+}

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
index 0000000..abaf3dd
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/control-panel/control-panel.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { ControlPanelComponent } from './control-panel.component';

+

+describe('ControlPanelComponent', () => {

+  let component: ControlPanelComponent;

+  let fixture: ComponentFixture<ControlPanelComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ ControlPanelComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(ControlPanelComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..f9144be
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/control-panel/control-panel.component.ts
@@ -0,0 +1,564 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, OnDestroy, HostListener, ViewChild, ElementRef } from '@angular/core';

+import { routerTransition } from 'app/router.animations';

+import { ActivatedRoute } from '@angular/router';

+import { TestExecutionService } from 'app/shared/services/test-execution.service';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+import BpmnJS from 'bpmn-js/lib/NavigatedViewer';

+import beautify from 'json-beautify';

+import { trigger, state, style, transition, animate } from '@angular/animations';

+import { interval } from 'rxjs';

+import { FileTransferService } from 'app/shared/services/file-transfer.service';

+import { Buffer } from 'buffer';

+import 'codemirror/mode/javascript/javascript.js';

+import { toInteger } from '@ng-bootstrap/ng-bootstrap/util/util';

+import { RequiredValidator } from '@angular/forms';

+import { UserService } from 'app/shared/services/user.service';

+import { ExecuteService } from 'app/shared/services/execute.service';

+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';

+import { Bpmn } from 'app/shared/models/bpmn.model';

+

+//import 'datatables.net';

+

+@Component({

+  selector: 'app-control-panel',

+  templateUrl: './control-panel.component.pug',

+  styleUrls: ['./control-panel.component.scss'],

+  animations: [routerTransition(),

+  trigger('detailExpand', [

+    state('collapsed', style({ height: '0px', minHeight: '0', display: 'none' })),

+    state('expanded', style({ height: '*' })),

+    transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),

+  ])

+  ]

+})

+export class ControlPanelComponent implements OnInit, OnDestroy {

+

+  @ViewChild('canvas-card') canvas: ElementRef;

+

+  public params;

+  public displayedColumns = ['startTime', 'endTime', 'totalTime'];

+  public dataSource = {};

+  public instanceDataSource = {};

+  public data = {};

+  public testHeads = [];

+  public testResult;

+  public expandedElement;

+  public selectedTestHead;

+  public selectedTestInstance;

+  public objectKeys = Object.keys;

+  private pullData = true;

+  public showFireworks = false;

+  public refreshData = false;

+  public spin = false;

+  public lastVTHResultsLength = 0;

+  // Create an Observable that will publish a value on an interval

+  public refreshCounter = interval(5000);

+

+  public isResizing = false;

+  public lastDownY;

+

+  public viewer: Bpmn;

+  public taskLog = '';

+

+  public executionJobLog = [];

+  public executionExternalTaskLog = [];

+  public executionVariables = [];

+

+  public executionJobLogDataSource;

+  public executionExternalTaskLogDataSource;

+  public executionVariablesDataSource;

+

+  public selectedExecutionJobLog;

+  public selectedExecutionExternalTaskLog;

+  public selectedExecutionVariable;

+

+

+  public codeConfig = {

+    mode: "application/json",

+    theme: "eclipse",

+    readonly: true,

+    lineNumbers: true

+  };

+

+  public taskLogConfig = {

+    mode: "Shell",

+    theme: "3024-night",

+    readonly: true

+  };

+

+  private processInstanceId;

+  public processState;

+

+

+  constructor(

+    private route: ActivatedRoute,

+    private executionService: ExecuteService,

+    private user: UserService,

+    private testExecution: TestExecutionService,

+    private fileTransfer: FileTransferService,

+    private bpmnFactory: BpmnFactoryService

+  ) { }

+

+  ngOnInit() {

+    this.route.queryParams.subscribe(params => {

+      this.params = params;

+      if(params.id){

+        this.refreshData = false;

+        this.populateData();

+

+        this.refreshCounter.subscribe(n => {

+          if (this.pullData){

+            this.populateData(n + 1);

+            this.updateFlowData();

+

+          }

+        });

+      }

+    });

+

+    $('#canvas-card').on('mousedown', (e) => {

+      this.isResizing = true;

+      this.lastDownY = $('#canvas-card').position().top + $('#canvas-card').outerHeight(true);

+    })

+

+    $(document).on('mousemove', (e) => {

+      if(!this.isResizing){

+        return;

+      }

+

+      var bottom = $('#canvas-card').position().top + $('#canvas-card').outerHeight(true);//$('#canvas-card').height() - (e.clientY - $('#canvas-card').offset().top);

+

+      if(bottom != this.lastDownY){

+        this.lastDownY = $('#canvas-card').position().top + $('#canvas-card').outerHeight(true);

+        this.onResize(null);

+      }

+

+    }).on('mouseup', () => {

+      this.isResizing = false;

+    })

+  }

+

+  ngOnDestroy() {

+    this.pullData = false;

+  }

+

+  refreshAllData() {

+    this.spin = true;

+    this.refreshData = true;

+    this.populateData();

+    this.updateFlowData();

+

+  }

+

+  populateData(loopNum = 0) {

+    this.testExecution.get(this.params.id).subscribe(

+      data => {

+        console.log(data);

+        let result = JSON.parse(JSON.stringify(data));

+

+        this.processInstanceId = result['processInstanceId'];

+

+        this.calcTime(result);

+

+        if(result['testInstanceResults']){

+          this.instanceDataSource = {};

+          for(var val = 0; val < result['testInstanceResults'].length; val++){

+            var elem = result['testInstanceResults'][val];

+            this.calcTime(elem);

+            let exists = false;

+            Object.keys(this.instanceDataSource).forEach((e, val) => {

+                if(e == elem.historicTestInstance._id){

+                    exists = true;

+                    return;

+                  }

+            });

+

+            if(!exists){

+              this.instanceDataSource[elem.historicTestInstance._id] = [elem];

+            }

+            else{

+                var found = false;

+

+                this.instanceDataSource[elem.historicTestInstance._id].forEach( (value, index) => {

+                    if(this.instanceDataSource[elem.historicTestInstance._id][index]._id == elem._id){

+                        this.instanceDataSource[elem.historicTestInstance._id][index] = elem;

+                        found = true;

+                    }

+                })

+                if(!found){

+                    this.instanceDataSource[elem.historicTestInstance._id].push(elem);

+                }

+            }

+            if(val == 0){

+              this.selectTestInstance(elem.historicTestInstance._id);

+            }

+          };

+        }

+

+        if (result['testHeadResults']) {

+          for (var i = 0 + this.lastVTHResultsLength; i < result['testHeadResults'].length; i++) {

+

+            var exists = false;

+            this.testHeads.forEach(elem => {

+              if (elem.testHeadId == result['testHeadResults'][i].testHeadId && elem.bpmnVthTaskId == result['testHeadResults'][i].bpmnVthTaskId) {

+                exists = true;

+              }

+            });

+

+            if (!exists) {

+              this.testHeads.push(result['testHeadResults'][i]);

+              this.dataSource[result['testHeadResults'][i].testHeadId + result['testHeadResults'][i].bpmnVthTaskId] = [];

+            }

+

+            let sDate = new Date(result['testHeadResults'][i].startTime);

+            let eDate = new Date(result['testHeadResults'][i].endTime);

+            let tDate = (eDate.getTime() - sDate.getTime()) / 1000;

+

+            result['testHeadResults'][i].startTime = sDate.getHours() + ":" + sDate.getMinutes() + ":" + sDate.getSeconds(); // + " " + sDate.getMonth() + "/" + sDate.getDate() + "/" + sDate.getFullYear();

+            result['testHeadResults'][i].endTime = eDate.getHours() + ":" + eDate.getMinutes() + ":" + eDate.getSeconds(); // + " " + eDate.getMonth() + "/" + eDate.getDate() + "/" + eDate.getFullYear();

+            result['testHeadResults'][i].totalTime = tDate + " secs";

+            result['testHeadResults'][i].testHeadRequestResponse = {

+                "testHeadRequest": result['testHeadResults'][i].testHeadRequest,

+                "testHeadResponse": result['testHeadResults'][i].testHeadResponse,

+                "statusCode": result['testHeadResults'][i].statusCode

+            };

+            //result['testHeadResults'][i].testHeadResponse = beautify(result['testHeadResults'][i].testHeadResponse, null, 2, 50);

+

+            this.dataSource[result['testHeadResults'][i].testHeadId + result['testHeadResults'][i].bpmnVthTaskId].push(result['testHeadResults'][i]);

+

+            if (i == 0) {

+              this.selectTestHead(result['testHeadResults'][i].testHeadId + result['testHeadResults'][i].bpmnVthTaskId);

+            }

+          }

+          //keep track of previous results so you don't reload them

+          this.lastVTHResultsLength = result['testHeadResults'].length;

+

+          this.testResult = Object.assign({}, result);

+          // this.user.get(result['executor']).subscribe(res => {

+          //   this.testResult['executor'] = res;

+          // });

+          //

+

+

+          this.spin = false;

+        }

+

+

+          //only gets called once

+        if (!this.refreshData && loopNum == 0 && (result['historicTestDefinition'] && result['historicTestDefinition']['bpmnInstances'][0])) {

+          let id = result['historicTestDefinition']['bpmnInstances'][0]['bpmnFileId']

+

+          if(!this.viewer){

+            this.bpmnFactory.setup({

+              mode: 'viewer',

+              options: {

+                container: '#canvas'

+              },

+              fileId: id

+            }).then(res => {

+              this.viewer = res;

+              this.updateFlowData();

+            });

+          }else{

+            this.bpmnFactory.getXml({

+              fileId: id

+            }).then(res => {

+              this.viewer.setBpmnXml(res);

+              this.updateFlowData();

+            })

+          }

+

+        }

+      }

+    );

+

+  }

+

+  updateExecutionData(){

+      if(this.executionJobLog){

+          this.executionJobLogDataSource = {};

+          for(var val = 0; val < this.executionJobLog.length; val++){

+              var elem = this.executionJobLog[val];

+

+              let exists = false;

+              Object.keys(this.executionJobLogDataSource).forEach((e, val) => {

+                  if(e == elem.activityId){

+                      exists = true;

+                      return;

+                  }

+              });

+

+              if(!exists){

+                  this.executionJobLogDataSource[elem.activityId] = [elem];

+              }

+              else{

+                  var found = false;

+

+                  this.executionJobLogDataSource[elem.activityId].forEach( (value, index) => {

+                      if(this.executionJobLogDataSource[elem.activityId][index].id == elem.id){

+                          this.executionJobLogDataSource[elem.activityId][index] = elem;

+                          found = true;

+                      }

+                  })

+                  if(!found){

+                      this.executionJobLogDataSource[elem.activityId].push(elem);

+                  }

+              }

+              if(val == 0){

+                  this.selectExecutionJobLog(elem.activityId);

+              }

+          };

+      }

+

+      if(this.executionExternalTaskLog){

+          this.executionExternalTaskLogDataSource = {};

+          for(var val = 0; val < this.executionExternalTaskLog.length; val++){

+              var elem = this.executionExternalTaskLog[val];

+

+              let exists = false;

+              Object.keys(this.executionExternalTaskLogDataSource).forEach((e, val) => {

+                  if(e == elem.activityId){

+                      exists = true;

+                      return;

+                  }

+              });

+

+              if(!exists){

+                  this.executionExternalTaskLogDataSource[elem.activityId] = [elem];

+              }

+              else{

+                  var found = false;

+

+                  this.executionExternalTaskLogDataSource[elem.activityId].forEach( (value, index) => {

+                      if(this.executionExternalTaskLogDataSource[elem.activityId][index].id == elem.id){

+                          this.executionExternalTaskLogDataSource[elem.activityId][index] = elem;

+                          found = true;

+                      }

+                  })

+                  if(!found){

+                      this.executionExternalTaskLogDataSource[elem.activityId].push(elem);

+                  }

+              }

+              if(val == 0){

+                  this.selectExecutionExternalTaskLog(elem.activityId);

+              }

+          };

+      }

+

+

+

+      if(this.executionVariables){

+          this.executionVariablesDataSource = {};

+          for(var val = 0; val < this.executionVariables.length; val++){

+              var elem = this.executionVariables[val];

+

+              let exists = false;

+              Object.keys(this.executionVariablesDataSource).forEach((e, val) => {

+                  if(e == elem.variableName){

+                      exists = true;

+                      return;

+                  }

+              });

+

+              if(!exists){

+                  this.executionVariablesDataSource[elem.variableName] = [elem];

+              }

+              else{

+                  var found = false;

+

+                  this.executionVariablesDataSource[elem.variableName].forEach( (value, index) => {

+                      if(this.executionVariablesDataSource[elem.variableName][index].id == elem.id){

+                          this.executionVariablesDataSource[elem.variableName][index] = elem;

+                          found = true;

+                      }

+                  })

+                  if(!found){

+                      this.executionVariablesDataSource[elem.variableName].push(elem);

+                  }

+              }

+              if(val == 0){

+                  this.selectExecutionVariable(elem.variableName);

+              }

+          };

+      }

+

+  }

+

+  calcTime(result) {

+    let tsDate = new Date(result['startTime']);

+    let teDate = new Date(result['endTime']);

+    let ttDate = (teDate.getTime() - tsDate.getTime()) / 1000;

+

+    result['date'] = tsDate.getMonth() + 1 + "/" + tsDate.getDate() + "/" + tsDate.getFullYear();

+    result['startTime'] = tsDate.getHours() + ":" + tsDate.getMinutes() + ":" + tsDate.getSeconds();

+    result['endTime'] = teDate.getHours() + ":" + teDate.getMinutes() + ":" + teDate.getSeconds();

+    result['totalTime'] = ttDate + ' secs';

+

+

+  }

+

+  updateFlowData() {

+    console.log(this.processInstanceId);

+    this.testExecution.status(this.processInstanceId).subscribe(

+      result => {

+        if (result) {

+          let data = result['body'];

+          //check process state

+          if (data.historicProcessInstance.state == 'COMPLETED') {

+            this.processState = 'Completed';

+            this.pullData = false;

+          } else if (data.historicProcessInstance.state == 'ACTIVE') {

+            this.processState = 'Running';

+          } else {

+            this.processState = 'Failed';

+            this.pullData = false;

+          }

+

+          if(data.historicJobLog){

+            this.executionJobLog =  data.historicJobLog;

+          }

+          if(data.historicExternalTaskLog){

+            this.executionExternalTaskLog =  data.historicExternalTaskLog;

+          }

+          if(data.historicVariableInstance){

+            this.executionVariables =  data.historicVariableInstance;

+          }

+          //update execution tabs -- job log, external task log, variables

+          this.updateExecutionData();

+

+

+            //loop through processes to get their info

+          for (let i = 0; i < data.historicActivityInstance.length; i++) {

+            let p = data.historicActivityInstance[i];

+            let state = null;

+            if (p.startTime && p.endTime && !p.canceled) { // process completed successfully

+              state = 'completed';

+            } else if (p.startTime && !p.endTime) { // process is still running

+              state = 'running';

+            } else if (p.canceled) {

+              state = 'failed';

+            }

+

+            //get task id

+

+            //highlight task boxes based on their state

+            this.viewer.getModel().get('canvas').addMarker(p.activityId, 'highlight-task-' + state);

+          }

+

+

+

+          for (let i = 0; i < data.historicIncident.length; i++) {

+            let p = data.historicIncident[i];

+            if (p.incidentMessage) {

+              this.taskLog += p.activityId + ': ' + p.incidentMessage + '\n';

+            }

+            this.viewer.getModel().get('canvas').addMarker(p.activityId, 'highlight-task-failed');

+          }

+        }

+      },

+      err => {

+

+      }

+    );

+  }

+

+  cancelExecution() {

+    this.executionService.delete(this.testResult._id).subscribe(result => {

+      this.updateFlowData();

+    });

+  }

+

+  expand(element) {

+    if (this.expandedElement == element)

+      this.expandedElement = null;

+    else

+      this.expandedElement = element;

+  }

+

+  beauty(json) {

+    return beautify(json, null, 2, 50);

+  }

+

+  @HostListener('window:resize', ['$event'])

+  onResize(event){

+    // console.log("hi")

+    if(this.viewer)

+      this.viewer.resize();

+  }

+

+

+  json2html(json: any = [{  }], tabs = 0) {

+    var html = '';

+    var tabHtml = '';

+    if (typeof json === 'string') {

+      json = JSON.parse(json);

+    }

+    for (let i = 0; i < tabs; i++) {

+      tabHtml += '&nbsp;&nbsp;&nbsp;&nbsp;';

+    }

+    for (let key in json) {

+      if (json.hasOwnProperty(key)) {

+        if (typeof json[key] === "object") {

+          html += tabHtml + '<b><u>' + key + ':</u></b><br/>';

+          if (json.constructor === Array && toInteger(key) > 0) {

+            tabs--;

+          }

+          html += this.json2html(json[key], ++tabs);

+        } else {

+          html += tabHtml + '<b><u>' + key + ':</u></b>' + '<br/>';

+          if (typeof json[key] === 'string') {

+            json[key] = json[key].replace(/\\n/g, '<br/>' + tabHtml);

+          }

+          html += tabHtml + json[key] + '<br/>';

+          html += '<br/>';

+        }

+      }

+    }

+    return html;

+  }

+

+  selectTestHead(key) {

+    this.selectedTestHead = key;

+  }

+

+  selectTestInstance(key){

+    this.selectedTestInstance = key;

+

+  }

+

+    selectExecutionJobLog(key){

+        this.selectedExecutionJobLog = key;

+

+    }

+

+    selectExecutionExternalTaskLog(key){

+        this.selectedExecutionExternalTaskLog = key;

+

+    }

+

+    selectExecutionVariable(key){

+        this.selectedExecutionVariable = key;

+

+    }

+

+  call() {

+    //

+  }

+

+}

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
index 0000000..ee11f53
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/control-panel/control-panel.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { ControlPanelModule } from './control-panel.module';

+

+describe('ControlPanelModule', () => {

+  let controlPanelModule: ControlPanelModule;

+

+  beforeEach(() => {

+    controlPanelModule = new ControlPanelModule();

+  });

+

+  it('should create an instance', () => {

+    expect(controlPanelModule).toBeTruthy();

+  });

+});

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
index 0000000..05f7b91
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/control-panel/control-panel.module.ts
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { ControlPanelRoutingModule } from './control-panel-routing.module';

+import { ControlPanelComponent } from './control-panel.component';

+import { FormsModule } from '@angular/forms';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { MatButtonModule, MatTableModule, MatFormFieldModule, MatInputModule, MatPaginatorModule, MatBadgeModule, MatCardModule, MatSelectModule, MatOptionModule, MatIconModule, MatTabsModule, MatListModule, MatDividerModule, MatExpansionModule } from '@angular/material';

+import { TestHeadModalModule } from 'app/shared/modules/test-head-modal/test-head-modal.module';

+import { AlertModalModule } from 'app/shared/modules/alert-modal/alert-modal.module';

+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';

+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

+import { CodemirrorModule } from 'ng2-codemirror';

+import { RobotReportComponent } from '../robot-report/robot-report.component';

+import { DataTablesModule } from 'angular-datatables';

+import { ResizableModule } from 'angular-resizable-element';

+import { NgxJsonViewerModule } from 'ngx-json-viewer';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    ControlPanelRoutingModule,

+    FormsModule,

+    FilterPipeModule,

+    MatButtonModule,

+    MatTableModule,

+    MatFormFieldModule,

+    MatInputModule,

+    MatPaginatorModule,

+    TestHeadModalModule,

+    AlertModalModule,

+    MatBadgeModule,

+    PerfectScrollbarModule,

+    MatCardModule,

+    MatSelectModule,

+    MatOptionModule,

+    MatIconModule,

+    NgbModule,

+    CodemirrorModule,

+    MatTabsModule,

+    MatListModule,

+    MatDividerModule,

+    MatExpansionModule,

+    DataTablesModule,

+    ResizableModule,

+    NgxJsonViewerModule

+  ],

+  declarations: [ControlPanelComponent, RobotReportComponent],

+  entryComponents: [RobotReportComponent]

+})

+export class ControlPanelModule { }

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
index 0000000..187bd80
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/dashboard/dashboard-routing.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { DashboardComponent } from './dashboard.component';

+

+const routes: Routes = [

+  {

+      path: '', component: DashboardComponent

+  }

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class DashboardRoutingModule { }

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
index 0000000..a823e68
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/dashboard/dashboard.component.pug
@@ -0,0 +1,115 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div(style="position: relative")

+  .row

+    .col-12

+      .pull-left

+        mat-form-field(style="width:110px")

+          input(matInput, [matDatepicker]="fromPicker", placeholder="From Date", [(ngModel)]="stats.filters.startDate")

+          mat-datepicker-toggle(matSuffix, [for]="fromPicker")

+          mat-datepicker(#fromPicker)

+        mat-form-field.ml-2(style="width:110px")

+          input(matInput, [matDatepicker]="toPicker", placeholder="To Date", [(ngModel)]="stats.filters.endDate")

+          mat-datepicker-toggle(matSuffix, [for]="toPicker")

+          mat-datepicker(#toPicker)

+        button.ml-2(mat-icon-button, (click)="stats.getDefaultData(_groups.getGroup())") 

+          mat-icon arrow_forward

+          

+      .pull-right

+        mat-form-field

+          input(matInput, [ngModel]="stats.executionList?.length", placeholder="Total Executions", disabled)

+  //- div

+  //-   button.pull-right(mat-button, (click)="openFilterModal()")

+  //-     mat-icon() filter_list

+  //-     span(style="font-size: 13px") Filter

+

+  //-   button.pull-right(mat-button, (click)="resetData()")

+  //-     mat-icon() refresh

+  //-     span(style="font-size: 13px") Reset

+

+  .row

+    .col-12

+      mat-card

+        mat-card-content

+          app-line-chart(height="201px")

+

+  .row.mt-2

+    .col-lg-5

+      mat-card

+        mat-card-header

+          mat-card-title 

+            h5 Test Results

+        mat-card-content

+          app-pie-chart(height="230px")

+    

+    .col-lg-7

+      mat-card

+        mat-card-header

+          mat-card-title 

+            h5 Test Definition Usage

+        mat-card-content

+          app-test-definition-executions-bar-chart(height="230px")

+  .row.mt-2

+    

+    .col-lg-7

+      mat-card

+        mat-card-header

+          mat-card-title 

+            h5 Virtual Test Head Executions

+        mat-card-content

+          app-test-head-executions-line-chart(height="230px")

+    

+    .col-lg-5

+      mat-card

+        mat-card-header

+          mat-card-title 

+            h5 Virtual Test Head Usage & Status Codes

+        mat-card-content

+          app-test-head-execution-bar-chart(height="230px")

+  //- mat-card.w-100

+  //-   mat-card-header

+  //-     mat-card-title(style="font-weight: bold") Selected Definitions: 

+  //-       span(style="color: #4F8CA9") {{TD_selectedTDs}}

+

+  //-   .row.mb-4  

+  //-     .col-md-7

+  //-       app-line-chart(height="380px")

+

+  //-     .col-md-5

+  //-       app-pie-chart(height="380px")

+

+  //- mat-card.w-100

+  //-   mat-card-header

+  //-     mat-card-title(style="font-weight: bold") Selected Instances: 

+  //-       span(style="color: #4F8CA9") {{TI_selectedTIs}}

+  //-     mat-card-title(style="font-weight: bold") Selected Definitions: 

+  //-       span(style="color: #4F8CA9") {{TI_selectedTDs}}

+  //-   .row.mb-4

+  //-     .col-md-7

+  //-       app-multi-line-chart(height="380px")

+  //-     .col-md-5

+  //-       app-horiz-bar-chart(height="380px")

+  

+  //- mat-card.w-100

+  //-   mat-card-header

+  //-     mat-card-title(style="font-weight: bold") Scheduled Tests 

+  //-     mat-card-title(style="font-weight: bold") Selected Instances: 

+  //-       span(style="color: #4F8CA9") {{sched_selectedTIs}}

+  //-   .row

+  //-     .col-md-4

+  //-       app-schedule

+

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
index 0000000..8099e27
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/dashboard/dashboard.component.scss
@@ -0,0 +1,82 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

+mat-card img{

+    object-fit: cover; /*this makes de image in src fit to the size of specified below*/

+    width: 100%; /* Here you can use wherever you want to specify the width and also the height of the <img>*/

+    margin: 0;

+}

+

+.dropdown-toggle::after {

+    display:none;

+}

+

+mat-card-content {

+    padding: 0px !important;

+    padding-top: 0px !important;

+}

+

+.mat-icon-button {

+    height: 20px !important;

+    padding: 0px !important;

+}

+

+.col-md-4, .col-md-5{

+    padding: 0px;

+    margin: 0px;

+}

+.col-md-3{

+

+    padding:0px;

+    margin: 0px;

+}

+

+//

+.shadow{

+    -moz-box-shadow:    inset 0 0 0 4px #2b2b2b;

+    -webkit-box-shadow: inset 0 0 0 4px #2b2b2b;

+    box-shadow:         inset 0 0 0 4px #2b2b2b;

+}

+

+.COMPLETED-dash {

+    background-color: #0d47a1;

+    color: white;

+}

+

+.SUCCESS-dash {

+    background-color: #199700;

+    color: white;

+}

+

+.FAILURE-dash {

+    background-color: #dd2c00 !important;

+    color: white;

+}

+

+.STOPPED-dash {

+    background-color: #ff9100;

+    color: white;

+}

+

+.UNAUTHORIZED-dash {

+    background-color: #000000;

+    color: white;

+}

+

+.UNKNOWN-dash {

+    background-color: White;

+}
\ 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
index 0000000..ec9b2eb
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/dashboard/dashboard.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { DashboardComponent } from './dashboard.component';

+

+describe('DashboardComponent', () => {

+  let component: DashboardComponent;

+  let fixture: ComponentFixture<DashboardComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ DashboardComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(DashboardComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..a01d427
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/dashboard/dashboard.component.ts
@@ -0,0 +1,173 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewChild, HostListener, EventEmitter, OnDestroy } from '@angular/core';

+import { routerTransition } from '../../router.animations';

+import { MatPaginator, MatDialog } from '@angular/material';

+import { ListService } from 'app/shared/services/list.service';

+import { TestExecutionService } from 'app/shared/services/test-execution.service';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+import { SchedulingService } from 'app/shared/services/scheduling.service';

+import { Subject, Observable, Subscription } from 'rxjs';

+import { UserService } from 'app/shared/services/user.service';

+import { FeathersService } from 'app/shared/services/feathers.service';

+import { GroupService } from 'app/shared/services/group.service';

+import { FilterModalComponent } from '../components/stats/filter-modal/filter-modal.component';

+import { StatsService } from '../components/stats/stats.service';

+

+@Component({

+  selector: 'app-dashboard',

+  templateUrl: './dashboard.component.pug',

+  styleUrls: ['./dashboard.component.scss'],

+  animations: [routerTransition()]

+})

+

+export class DashboardComponent implements OnInit, OnDestroy {

+

+  private toDestroy: Array<Subscription> = [];

+

+  // Top of the page stats

+  public topStats = {

+    COMPLETED: 0,

+    UNKNOWN: 0,

+    FAILURE: 0,

+    STOPPED: 0,

+    UNAUTHORIZED: 0,

+    FAILED: 0

+  };

+

+  public testDefinitionList = null;

+  public testExecutions;

+  public displayedColumns = ['name', 'result', 'startTime'];

+  public displayedScheduleColumns = ['name', 'nextRun'];

+  public weekExecutions = 0;

+  public weekSchedules = 0;

+  public filter = { testResult: '' }; // for dropdown in html

+  public group;

+

+  public eventsSubject: Subject<void>;

+

+  public TD_selectedTDs = "All";

+  public TI_selectedTDs = "All";

+  public TI_selectedTIs = "Top 5";

+  public sched_selectedTIs = "All";

+

+  public viewers = [];

+

+  @ViewChild(MatPaginator) executionsPaginator: MatPaginator;

+  @ViewChild(MatPaginator) scheduledPaginator: MatPaginator;

+

+  constructor(

+    private _groups: GroupService,

+    private filterModal: MatDialog, 

+    private stats: StatsService

+  ) { }

+

+  async ngOnInit() {

+

+    this.stats.getDefaultData(this._groups.getGroup());

+    this.toDestroy.push(this._groups.groupChange().subscribe(group => {

+      this.stats.getDefaultData(group);

+    }));

+

+    //this.resetData();

+

+  //   this.stats.onTDExecutionChangeFinished().subscribe(res => {

+  //     this.TD_selectedTDs = "";

+  //     this.stats.getTDFilters().selected.forEach(item => {

+  //       this.TD_selectedTDs += (item.viewValue + ", ");

+  //     })

+  //     let charLimit = 200;

+  //     if (this.TD_selectedTDs.length > charLimit) {

+  //       this.TD_selectedTDs = this.TD_selectedTDs.slice(0, charLimit) + "...";

+  //     } else this.TD_selectedTDs = this.TD_selectedTDs.slice(0, this.TD_selectedTDs.length - 2);

+  //   })

+

+  //   this.stats.onTIExecutionChangeFinished().subscribe(res => {

+  //     let selectedTIs = this.stats.getTIFilters().selectedTIs;

+  //     let selectedTDs = this.stats.getTIFilters().selectedTDs;

+

+  //     if (selectedTIs.length == 0) this.TI_selectedTIs = "All";

+  //     else {

+  //       this.TI_selectedTIs = "";

+  //       this.stats.getTIFilters().selectedTIs.forEach(item => {

+  //         this.TI_selectedTIs += (item + ", ");

+  //       })

+  //       let charLimit = 200;

+  //       if (this.TI_selectedTIs.length > charLimit) {

+  //         this.TI_selectedTIs = this.TI_selectedTIs.slice(0, charLimit) + "...";

+  //       } else this.TI_selectedTIs = this.TI_selectedTIs.slice(0, this.TI_selectedTIs.length - 2);

+  //     }

+

+  //     if (selectedTDs.length == 0) this.TI_selectedTDs = "All";

+  //     else {

+  //       this.TI_selectedTDs = "";

+  //       this.stats.getTIFilters().selectedTDs.forEach(item => {

+  //         this.TI_selectedTDs += (item + ", ");

+  //       })

+  //       let charLimit = 200;

+  //       if (this.TI_selectedTDs.length > charLimit) {

+  //         this.TI_selectedTDs = this.TI_selectedTDs.slice(0, charLimit) + "...";

+  //       } else this.TI_selectedTDs = this.TI_selectedTDs.slice(0, this.TI_selectedTDs.length - 2);

+  //     }

+  //   })

+

+  //   this.stats.onScheduleChangeFinished().subscribe(res => {

+  //     let selectedTIs = this.stats.scheduledTests.map(el => el.name);

+  //     //console.log(selectedTIs);

+  //     if (selectedTIs.length == 0) this.sched_selectedTIs = "All";

+  //     else {

+  //       this.sched_selectedTIs = "";

+  //       this.stats.scheduledTests.map(el => el.name).forEach(item => {

+  //         this.sched_selectedTIs += (item + ", ");

+  //       })

+  //       let charLimit = 200;

+  //       if (this.sched_selectedTIs.length > charLimit) {

+  //         this.sched_selectedTIs = this.sched_selectedTIs.slice(0, charLimit) + "...";

+  //       } else this.sched_selectedTIs = this.sched_selectedTIs.slice(0, this.sched_selectedTIs.length - 2);

+  //     }

+  //   })

+  }

+

+  ngOnDestroy(){

+    this.toDestroy.forEach(elem => {

+      elem.unsubscribe();

+    });

+  }

+

+  openFilterModal() {

+    let open = this.filterModal.open(FilterModalComponent, {

+      width: '50%',

+      height: '60%',

+      disableClose: true

+    })

+

+    open.afterClosed().subscribe(res => {

+      this.ngOnInit();

+    })

+  }

+

+  resetData() {

+    //console.log("resetting");

+    this.TD_selectedTDs = "All";

+    this.TI_selectedTDs = "All";

+    this.TI_selectedTIs = "Top 5";

+    this.sched_selectedTIs = "All";

+    this.stats.getDefaultData(this._groups.getGroup());

+  }

+

+}

+

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
index 0000000..9db6f12
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/dashboard/dashboard.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { DashboardModule } from './dashboard.module';

+

+describe('DashboardModule', () => {

+  let dashboardModule: DashboardModule;

+

+  beforeEach(() => {

+    dashboardModule = new DashboardModule();

+  });

+

+  it('should create an instance', () => {

+    expect(dashboardModule).toBeTruthy();

+  });

+});

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
index 0000000..d6545e9
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/dashboard/dashboard.module.ts
@@ -0,0 +1,110 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { DashboardRoutingModule } from './dashboard-routing.module';

+import { DashboardComponent } from './dashboard.component';

+import { FormsModule } from '@angular/forms';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+

+import {

+    MatBadgeModule,

+    MatButtonModule,

+    MatCardModule,

+    MatFormFieldModule,

+    MatIconModule,

+    MatInputModule,

+    MatOptionModule,

+    MatPaginatorModule,

+    MatSelectModule,

+    MatTableModule,

+    MatTabsModule,

+    MatCheckboxModule,

+    MatDialogModule,

+    MAT_DIALOG_DEFAULT_OPTIONS,

+    MatExpansionModule,

+    MatDatepickerModule,

+    MatNativeDateModule,

+    MatProgressSpinnerModule,

+} from '@angular/material';

+import { TestHeadModalModule } from 'app/shared/modules/test-head-modal/test-head-modal.module';

+import { AlertModalModule } from 'app/shared/modules/alert-modal/alert-modal.module';

+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';

+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

+import { TestDefinitionExpandedDetailsComponent } from '../test-definition-expanded-details/test-definition-expanded-details.component';

+import { ViewWorkflowModalModule } from 'app/shared/modules/view-workflow-modal/view-workflow-modal.module';

+import { PieChartComponent } from '../components/stats/pie-chart/pie-chart.component';

+import { LineChartComponent } from '../components/stats/line-chart/line-chart.component'

+import { ScheduleComponent } from '../components/stats/schedule/schedule.component';;

+import { HorizBarChartComponent } from '../components/stats/horiz-bar-chart/horiz-bar-chart.component';

+import { FilterModalComponent } from '../components/stats/filter-modal/filter-modal.component';

+import { MultiLineChartComponent } from '../components/stats/multi-line-chart/multi-line-chart.component';

+import { TestDefinitionExecutionsBarChartComponent } from '../components/stats/test-definition-executions-bar-chart/test-definition-executions-bar-chart.component';

+import { TestHeadExecutionsLineChartComponent } from '../components/stats/test-head-executions-line-chart/test-head-executions-line-chart.component';

+import { TestHeadExecutionBarChartComponent } from '../components/stats/test-head-execution-bar-chart/test-head-execution-bar-chart.component';

+

+@NgModule({

+    imports: [

+        CommonModule,

+        DashboardRoutingModule,

+        FormsModule,

+        FilterPipeModule,

+        MatButtonModule,

+        MatTableModule,

+        MatFormFieldModule,

+        MatInputModule,

+        MatPaginatorModule,

+        TestHeadModalModule,

+        AlertModalModule,

+        MatBadgeModule,

+        PerfectScrollbarModule,

+        MatCardModule,

+        MatSelectModule,

+        MatOptionModule,

+        MatIconModule,

+        NgbModule,

+        MatCheckboxModule,

+        MatTabsModule,

+        ViewWorkflowModalModule,

+        MatDialogModule,

+        MatExpansionModule,

+        MatDatepickerModule,

+        MatNativeDateModule,

+        MatProgressSpinnerModule

+    ],

+    declarations: [

+        DashboardComponent,

+        TestDefinitionExpandedDetailsComponent,

+        LineChartComponent,

+        MultiLineChartComponent,

+        ScheduleComponent,

+        PieChartComponent,

+        HorizBarChartComponent,

+        FilterModalComponent,

+        TestDefinitionExecutionsBarChartComponent,

+        TestHeadExecutionsLineChartComponent,

+        TestHeadExecutionBarChartComponent

+    ],

+    entryComponents: [TestDefinitionExpandedDetailsComponent, FilterModalComponent],

+    schemas: [CUSTOM_ELEMENTS_SCHEMA],

+    exports: [FilterModalComponent, LineChartComponent],

+    providers: [{ provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { hasBackdrop: true } }, MatDatepickerModule]

+

+

+})

+export class DashboardModule {

+}

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
index 0000000..92fee59
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/feedback/feedback-routing.module.ts
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {RouterModule, Routes} from '@angular/router';

+import {FeedbackComponent} from './feedback.component';

+

+const routes: Routes = [

+    {path: '', component: FeedbackComponent}

+];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class FeedbackRoutingModule {

+}

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
index 0000000..b38e410
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/feedback/feedback.component.pug
@@ -0,0 +1,45 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+    h2 Submit Feedback

+    hr

+

+// name label & form input

+form([formGroup]='FeedbackFormGroup')

+    .row

+        .col-sm-2

+            mat-form-field(style='width:100%')

+                input(matInput, placeholder='First Name', formControlName='firstName', required)

+                mat-error Required

+        .col-sm-2

+            mat-form-field(style='width:100%')

+                input(matInput, placeholder='Last Name', formControlName='lastName', required)

+                mat-error Required

+    .row

+        .col-sm-4

+            // email label & form input

+            mat-form-field(style='width:100%')

+                input(matInput, placeholder='example@.com', type='email', formControlName='email', required)

+    .row

+        .col-sm-4

+            // message label & form input

+            mat-form-field(style='width:100%')

+                textarea(matInput, placeholder='Message', formControlName='message', required, rows=15)

+    .row

+        .col-sm-4

+            // button for submitting the information in the form

+            button(mat-raised-button='', color='primary', class='pull-right', (click)='onSubmitFeedback()') Send Feedback

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
index 0000000..70fad06
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/feedback/feedback.component.scss
@@ -0,0 +1,17 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

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
index 0000000..c79e8c3
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/feedback/feedback.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {async, ComponentFixture, TestBed} from '@angular/core/testing';

+

+import {FeedbackComponent} from './feedback.component';

+

+describe('FeedbackComponent', () => {

+    let component: FeedbackComponent;

+    let fixture: ComponentFixture<FeedbackComponent>;

+

+    beforeEach(async(() => {

+        TestBed.configureTestingModule({

+            declarations: [FeedbackComponent]

+        })

+            .compileComponents();

+    }));

+

+    beforeEach(() => {

+        fixture = TestBed.createComponent(FeedbackComponent);

+        component = fixture.componentInstance;

+        fixture.detectChanges();

+    });

+

+    it('should create', () => {

+        expect(component).toBeTruthy();

+    });

+});

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
index 0000000..ed1be7f
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/feedback/feedback.component.ts
@@ -0,0 +1,98 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {Component, OnInit} from '@angular/core';

+import {routerTransition} from 'app/router.animations';

+import {FormControl, FormGroup, Validators} from '@angular/forms';

+import {MatDialog} from '@angular/material';

+import {AlertModalComponent} from '../../shared/modules/alert-modal/alert-modal.component';

+import {FeedbackService} from "../../shared/services/feedback.service";

+

+@Component({

+    selector: 'app-feedback',

+    templateUrl: './feedback.component.pug',

+    styleUrls: ['./feedback.component.scss'],

+    animations: [routerTransition()]

+})

+export class FeedbackComponent implements OnInit {

+    private firstName: string;

+    private lastName: string;

+    private email: string;

+    private message: string;

+

+    public FeedbackFormGroup: FormGroup;

+    private FirstNameFormControl: FormControl;

+    private LastNameFormControl: FormControl;

+    private EmailFormControl: FormControl;

+    private MessageFormControl: FormControl;

+

+    constructor(

+        private ResponseMatDialog: MatDialog,

+        private feedback: FeedbackService

+    ) {

+    }

+

+

+    // @ViewChild('feedbackForm') private FeedBackForm;

+

+    ngOnInit(): void {

+        this.createFormControls();

+        this.createFormGroup();

+    }

+

+    private createFormControls() {

+        this.FirstNameFormControl = new FormControl('', [Validators.required]);

+        this.LastNameFormControl = new FormControl('', [Validators.required]);

+        this.EmailFormControl = new FormControl('', [Validators.required, Validators.email]);

+        this.MessageFormControl = new FormControl('', [Validators.required]);

+    }

+

+    private createFormGroup() {

+        this.FeedbackFormGroup = new FormGroup({

+            firstName: this.FirstNameFormControl,

+            lastName: this.LastNameFormControl,

+            email: this.EmailFormControl,

+            message: this.MessageFormControl

+        });

+    }

+

+    // submit button action

+    public onSubmitFeedback() {

+        if (!this.FeedbackFormGroup.invalid) {

+            // console.log(this.FeedbackFormGroup.getRawValue())

+            this.feedback.sendFeedback(this.FeedbackFormGroup.getRawValue()).subscribe(

+                (result) => {

+                    this.sendFeedbackAlert('ok', 'Feedback sent!');

+                },

+                (error) => {

+                    this.sendFeedbackAlert('warning', 'Please verify form fields are correct.');

+                }

+            )        }

+        else{

+            this.sendFeedbackAlert('warning', 'Please verify form fields are correct.');

+        }

+    }

+

+    private sendFeedbackAlert(type: string, message: string) {

+        this.ResponseMatDialog.open(AlertModalComponent, {

+            width: '250px',

+            data: {

+                type: type,

+                message: message

+            }

+        });

+    }

+}

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
index 0000000..b27d504
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/feedback/feedback.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {FeedbackModule} from './feedback.module';

+

+describe('FeedbackModule', () => {

+    let feedbackModule: FeedbackModule;

+

+    beforeEach(() => {

+        feedbackModule = new FeedbackModule();

+    });

+

+    it('should create an instance', () => {

+        expect(feedbackModule).toBeTruthy();

+    });

+});

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
index 0000000..d614de2
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/feedback/feedback.module.ts
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {CommonModule} from '@angular/common';

+import {FeedbackComponent} from './feedback.component';

+import {FormsModule, ReactiveFormsModule} from '@angular/forms';

+import {FilterPipeModule} from 'ngx-filter-pipe';

+import {MatBadgeModule, MatButtonModule, MatCardModule, MatIconModule, MatInputModule} from '@angular/material';

+import {AlertModalModule} from 'app/shared/modules/alert-modal/alert-modal.module';

+import {PerfectScrollbarModule} from 'ngx-perfect-scrollbar';

+import {NgbModule} from '@ng-bootstrap/ng-bootstrap';

+import {FeedbackRoutingModule} from './feedback-routing.module';

+

+@NgModule({

+    imports: [

+        CommonModule,

+        FeedbackRoutingModule,

+        FormsModule,

+        ReactiveFormsModule,

+        FilterPipeModule,

+        MatButtonModule,

+        MatInputModule,

+        AlertModalModule,

+        MatBadgeModule,

+        PerfectScrollbarModule,

+        MatCardModule,

+        MatIconModule,

+        NgbModule,

+    ],

+    declarations: [

+        FeedbackComponent

+    ]

+})

+export class FeedbackModule {

+}

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
index 0000000..278d3b7
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/layout-routing.module.ts
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {RouterModule, Routes} from '@angular/router';

+import {LayoutComponent} from './layout.component';

+import {AdminGuard} from "../shared/guard/admin.guard";

+

+const routes: Routes = [

+    {

+        path: '',

+        component: LayoutComponent,

+        children: [

+            {path: '', redirectTo: 'dashboard', pathMatch: 'prefix'},

+            {path: 'test-definitions', loadChildren: './tests/tests.module#TestsModule'},

+            {path: 'settings', loadChildren: './settings/settings.module#SettingsModule'},

+            {path: 'manage-group', loadChildren: './manage-group/manage-group.module#ManageGroupModule'},

+            {path: 'feedback', loadChildren: './feedback/feedback.module#FeedbackModule'},

+            {path: 'dashboard', loadChildren: './dashboard/dashboard.module#DashboardModule'},

+            {path: 'scheduling', loadChildren: './scheduling/scheduling.module#SchedulingModule'},

+            {path: 'onboarding', loadChildren: './onboarding/onboarding.module#OnboardingModule'},

+            {path: 'control-panel', loadChildren: './control-panel/control-panel.module#ControlPanelModule'},

+            {path: 'test-heads', loadChildren: './virtual-test-heads/virtual-test-heads.module#VirtualTestHeadsModule'},

+            {path: 'test-instances', loadChildren: './test-instances-catalog/test-instances-catalog.module#TestInstancesCatalogModule'},

+            {path: 'test-executions', loadChildren: './test-executions-catalog/test-executions-catalog.module#TestExecutionsCatalogModule'},

+            {path: 'user-management', loadChildren: './user-management/user-management.module#UserManagementModule', canActivate: [AdminGuard]},

+            {path: 'modeler', loadChildren: './modeler/modeler.module#ModelerModule'}

+            

+        ]

+    }

+];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class LayoutRoutingModule {

+}

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
index 0000000..a809598
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/layout.component.html
@@ -0,0 +1,21 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<app-header></app-header>

+<app-sidebar></app-sidebar>

+<section class="main-container">

+    <router-outlet></router-outlet>

+</section>

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
index 0000000..2ac9020
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/layout.component.scss
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.main-container {

+    margin-top: 56px;

+    margin-left: 235px;

+    padding: 15px;

+    -ms-overflow-x: hidden;

+    overflow-x: hidden;

+    overflow-y: scroll;

+    position: relative;

+    overflow: hidden;

+}

+@media screen and (max-width: 992px) {

+    .main-container {

+        margin-left: 0px !important;

+    }

+}

+@media print {

+    .main-container {

+        margin-top: 0px !important;

+        margin-left: 0px !important;

+    }

+}
\ 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
index 0000000..5fd88f2
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/layout.component.spec.ts
@@ -0,0 +1,55 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {async, ComponentFixture, TestBed} from '@angular/core/testing';

+import {RouterTestingModule} from '@angular/router/testing';

+import {TranslateModule} from '@ngx-translate/core';

+

+import {LayoutComponent} from './layout.component';

+import {LayoutModule} from './layout.module';

+

+describe('LayoutComponent', () => {

+    let component: LayoutComponent;

+    let fixture: ComponentFixture<LayoutComponent>;

+

+    beforeEach(

+        async(() => {

+            TestBed.configureTestingModule({

+                imports: [

+                    LayoutModule,

+                    RouterTestingModule,

+                    TranslateModule.forRoot(),

+                ]

+            }).compileComponents()

+                .then((arg) => {

+                    // handle

+                })

+                .catch((err) => {

+                    // handle

+                });

+        })

+    );

+

+    beforeEach(() => {

+        fixture = TestBed.createComponent(LayoutComponent);

+        component = fixture.componentInstance;

+        fixture.detectChanges();

+    });

+

+    it('should create', () => {

+        expect(component).toBeTruthy();

+    });

+});

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
index 0000000..07e06c1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/layout.component.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+

+@Component({

+    selector: 'app-layout',

+    templateUrl: './layout.component.html',

+    styleUrls: ['./layout.component.scss']

+})

+export class LayoutComponent implements OnInit {

+    constructor() {}

+

+    ngOnInit() {}

+}

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
index 0000000..ff64010
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/layout.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { LayoutModule } from './layout.module';

+

+describe('LayoutModule', () => {

+    let layoutModule: LayoutModule;

+

+    beforeEach(() => {

+        layoutModule = new LayoutModule();

+    });

+

+    it('should create an instance', () => {

+        expect(layoutModule).toBeTruthy();

+    });

+});

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
index 0000000..1de7fa6
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/layout.module.ts
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { TranslateModule } from '@ngx-translate/core';

+import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';

+

+import { LayoutRoutingModule } from './layout-routing.module';

+import { LayoutComponent } from './layout.component';

+import { SidebarComponent } from './components/sidebar/sidebar.component';

+import { HeaderComponent } from './components/header/header.component';

+import { RightSidebarComponent } from './components/right-sidebar/right-sidebar.component';

+import { CreateGroupModalModule } from 'app/shared/modules/create-group-modal/create-group-modal.module';

+import { MatMenuModule, MatIconModule, MatButtonModule } from '@angular/material';

+import { MenuItemComponent } from 'app/shared/components/menu-item/menu-item.component';

+@NgModule({

+    imports: [

+        CommonModule,

+        LayoutRoutingModule,

+        TranslateModule,

+        NgbDropdownModule.forRoot(),

+        CreateGroupModalModule,

+        MatMenuModule,

+        MatIconModule,

+        MatButtonModule

+    ],

+    declarations: [

+        LayoutComponent, 

+        SidebarComponent,

+        HeaderComponent, 

+        RightSidebarComponent,

+        MenuItemComponent

+    ]

+})

+export class LayoutModule {}

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
index 0000000..03ba7da
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.pug
@@ -0,0 +1,26 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+

+h4.mb-2.ml-1(style="font-weight: bold;") Change Roles - {{user?.firstName}} {{user?.lastName}}

+  //input.ml-1(matInput, type='search', placeholder='Search...', color='blue', [(ngModel)]='search.roleName')

+div(style="max-height: 300px; overflow-y: scroll")

+  .px-4.py-3

+    .mr-2.ml-2(*ngFor="let role of roles")

+      mat-checkbox([(ngModel)]="role.isSelected") {{role.roleName}} 

+div(style="text-align: center")            

+  button.primary.mr-1(mat-raised-button, [disabled]= "", aria-label='Edit', color="primary", (click)='saveRoles()') Save

+      

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..1eca9b5
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { DropdownMultiselectComponent } from './dropdown-multiselect.component';

+

+describe('DropdownMultiselectComponent', () => {

+  let component: DropdownMultiselectComponent;

+  let fixture: ComponentFixture<DropdownMultiselectComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ DropdownMultiselectComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(DropdownMultiselectComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..df065cb
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/dropdown-multiselect.component.ts
@@ -0,0 +1,96 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject } from '@angular/core';

+import { GroupService } from 'app/shared/services/group.service';

+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

+import { UserService } from 'app/shared/services/user.service';

+

+

+@Component({

+  selector: 'app-dropdown-multiselect',

+  templateUrl: './dropdown-multiselect.component.pug',

+  styleUrls: ['./dropdown-multiselect.component.scss']

+})

+export class DropdownMultiselectComponent implements OnInit {

+

+  public group;

+  public memberRoles;

+  private params;

+  public roles;

+  public user;

+  public userId;

+  public search;

+  constructor(private groupService: GroupService, public dialogRef: MatDialogRef<DropdownMultiselectComponent>,

+    private userService: UserService,

+    @Inject(MAT_DIALOG_DATA) public input_data

+  ) {

+   

+   }

+

+  ngOnInit() {

+    this.search = {};

+    this.userId = this.input_data["user"][0]["_id"];

+    this.group = this.input_data["group"];

+    this.memberRoles = this.group["members"].filter(member => member.userId == this.userId)["roles"];

+    this.userService.get(this.userId).subscribe((result) => {

+      this.user = result;

+    });

+    this.roles = this.group.roles;

+    

+    this.memberRoles = this.group.members.filter(member => member.userId.toString() == this.userId.toString())[0].roles;

+    if(this.memberRoles){

+      for(let i = 0; i < this.roles.length; i++){

+        this.roles[i].isSelected = false;

+        for(let j = 0; j < this.memberRoles.length; j++){

+          if(this.roles[i].roleName == this.memberRoles[j]){

+            this.roles[i].isSelected = true;

+          }

+        }

+      }

+    }

+  }

+

+  saveRoles(){

+    let member = {

+      userId : this.userId,

+      roles : []

+    }

+    

+    member.roles = this.roles.filter(role => role.isSelected).map(item => {return item.roleName});

+    

+    // the logic to remove the one member from the array of members and then push the new member roles

+    this.groupService.get(this.group._id).subscribe((res) => {

+      let group = res;

+      

+      let newMembers = [];

+      if(group["members"]){

+        newMembers = group["members"].filter(member => member.userId.toString() != this.userId.toString());

+      }

+      newMembers.push(member)

+      let groupPatch = {

+        _id : this.group._id,

+        members : newMembers

+      }

+      this.groupService.patch(groupPatch).subscribe((response) => {

+        this.dialogRef.close();

+      });

+    });

+    

+    

+  }

+

+}

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
index 0000000..d4de15a
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.pug
@@ -0,0 +1,96 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+  .card-md-12

+    .pull-left

+      app-page-header([heading]="'Custom Role Management'") 

+      h5 {{ groupName }}

+      

+      

+

+    .pull-right

+      button(mat-raised-button color="primary", (click)="update()") Update All Rows

+      

+  .card-md-12.mt-3

+

+    div(style="width: 100%", [hidden]="!loading")

+      mat-spinner(style="margin: auto", color="primary")

+    table(mat-table, [dataSource]="dataSource", style="width: 100%", [hidden]="loading")

+

+      ng-container(matColumnDef="roleName")

+        th(mat-header-cell, *matHeaderCellDef) Role Name

+        td(mat-cell, *matCellDef="let element") {{ element.roleName }}

+

+      ng-container(matColumnDef="read")

+        th(mat-header-cell, *matHeaderCellDef) Read

+        td(mat-cell, *matCellDef="let element")

+          mat-checkbox([(ngModel)]="element.readPermission", [disabled]="true") 

+          

+

+

+

+      ng-container(matColumnDef="write")

+        th(mat-header-cell, *matHeaderCellDef) Write

+        td(mat-cell, *matCellDef="let element") 

+          mat-checkbox([(ngModel)]="element.writePermission", [disabled]="(element.roleName == 'admin')") 

+          

+      ng-container(matColumnDef="execute")

+        th(mat-header-cell, *matHeaderCellDef) Execute

+        td(mat-cell, *matCellDef="let element")

+          mat-checkbox([(ngModel)]="element.executePermission", [disabled]="(element.roleName == 'admin')")

+         

+      ng-container(matColumnDef="delete")

+        th(mat-header-cell, *matHeaderCellDef) Delete

+        td(mat-cell, *matCellDef="let element") 

+          mat-checkbox([(ngModel)]="element.deletePermission", [disabled]="(element.roleName == 'admin')") 

+

+      ng-container(matColumnDef="management")

+        th(mat-header-cell, *matHeaderCellDef) Management

+        td(mat-cell, *matCellDef="let element")

+          mat-checkbox([(ngModel)]="element.managementPermission", [disabled]="true") 

+

+      ng-container(matColumnDef="actions")

+        th(mat-header-cell, *matHeaderCellDef) Actions

+        td(mat-cell, *matCellDef="let element")

+          button(color="warn", matTooltip="Delete Role Permissions", mat-icon-button, (click)="deleteRole(element)", [disabled]="((element.roleName == 'admin') || (element.roleName == 'user') || (element.roleName == 'developer'))")

+            mat-icon delete_forever

+          button(color="primary", matTooltip="Save Role Permissions", mat-icon-button, (click)="update()", [disabled]="(element.roleName == 'admin')")

+            mat-icon save

+          

+

+

+

+      tr(mat-header-row *matHeaderRowDef="displayedColumns")

+      tr(mat-row *matRowDef="let row; columns: displayedColumns;")

+

+     

+

+

+

+  div(style="width: 100%;height:50px")

+

+  .card-md-12

+    .row

+      .col-sm-4

+        h3 Add New Role

+    .row

+      .col-sm-4

+        mat-form-field

+          input(matInput [(ngModel)]="roleName", id="roleName", name="roleName", placeholder="Role Name", required)

+        button(mat-raised-button color="primary", (click)="create()", style="margin-left:20px;", [disabled]="!roleName") Add Role

+

+

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..f6bab0d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { ManageGroupRolesComponent } from './manage-group-roles.component';

+

+describe('ManageGroupRolesComponent', () => {

+  let component: ManageGroupRolesComponent;

+  let fixture: ComponentFixture<ManageGroupRolesComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ ManageGroupRolesComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(ManageGroupRolesComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..c637e2b
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group-roles/manage-group-roles.component.ts
@@ -0,0 +1,243 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { routerLeftTransition } from 'app/router.animations';

+import { ActivatedRoute } from '@angular/router';

+import { GroupService } from 'app/shared/services/group.service';

+import { CookieService } from 'ngx-cookie-service';

+import { MatTableDataSource } from '@angular/material/table';

+import { MatDialog } from '@angular/material';

+import {AlertModalComponent} from '../../../shared/modules/alert-modal/alert-modal.component';

+import { NEXT } from '@angular/core/src/render3/interfaces/view';

+import { convertPropertyBindingBuiltins } from '@angular/compiler/src/compiler_util/expression_converter';

+import { extractDirectiveDef } from '@angular/core/src/render3/definition';

+import { take } from 'rxjs/operators';

+

+

+

+

+

+@Component({

+  selector: 'app-manage-group-roles',

+  templateUrl: './manage-group-roles.component.pug',

+  styleUrls: ['./manage-group-roles.component.scss'],

+  animations: [routerLeftTransition()]

+})

+

+export class ManageGroupRolesComponent implements OnInit {

+

+

+  public selectedGroup;

+  public groupRoles;

+  public roleName = null;

+  public loading = false;

+  public groupPermissions;

+  public element = {};

+  public groupName;

+

+  public dataSource;

+  public displayedColumns;

+  public readPermission = false;

+  public writePermission = false;

+  public executePermission = false;

+  public deletePermission = false;

+  public managementPermission = false;

+

+  

+

+

+

+  constructor(

+    public _groups: GroupService,

+    private cookie: CookieService,

+    private modal: MatDialog,

+    private ResponseMatDialog: MatDialog

+    

+  ) { 

+

+    

+  }

+

+  ngOnInit() {

+

+    this.setComponentData(this._groups.getGroup());

+    this._groups.groupChange().subscribe(group => {

+      this.setComponentData(group);

+

+    });

+

+

+  }

+

+  setComponentData(group) {

+    if(!group){

+      return;

+    }

+    this.groupName = group.groupName;

+    this.loading = true;

+    this.dataSource = new MatTableDataSource();

+    this._groups.find({ 

+      $limit: -1,

+      _id: group['_id'], 

+      $select: ['_id', 'roles']

+    }).subscribe((res) => {

+

+      this.selectedGroup = res[0];

+      this.groupRoles = res[0].roles;

+

+      //If current group does not have any roles

+      if ( (this.groupRoles == null) || (this.groupRoles.length < 1))

+      {

+        this.groupRoles = [

+          {roleName: "admin", permissions: "read, write, execute, delete, management"},

+          {roleName: "user", permissions: "read"},

+          {roleName: "developer", permissions: "read, write, execute, delete"}

+        ];

+        

+      }

+

+

+      for (let i = 0; i < this.groupRoles.length; i++){

+        this.groupRoles[i].readPermission = false;

+        this.groupRoles[i].writePermission = false;

+        this.groupRoles[i].executePermission = false;

+        this.groupRoles[i].deletePermission = false;

+        this.groupRoles[i].managementPermission = false;

+        if (this.groupRoles[i].permissions.includes('read')){

+          this.groupRoles[i].readPermission = true;

+        }

+        if (this.groupRoles[i].permissions.includes('write')){

+          this.groupRoles[i].writePermission = true;

+        }

+        if (this.groupRoles[i].permissions.includes('execute')){

+          this.groupRoles[i].executePermission = true;

+        }

+        if (this.groupRoles[i].permissions.includes('delete')){

+          this.groupRoles[i].deletePermission = true;

+        }

+        if (this.groupRoles[i].permissions.includes('management')){

+          this.groupRoles[i].managementPermission = true;

+        }

+      }

+     

+      this.dataSource.data = this.groupRoles;

+      this.loading = false;

+      this.update();

+

+      

+

+

+    })

+

+    this.displayedColumns = ['roleName', 'read', 'write', 'execute', 'delete', 'management', 'actions']

+

+  }

+

+

+  async create(){

+    

+    for (let i = 0; i < this.groupRoles.length; i++){

+      if (this.groupRoles[i].roleName == this.roleName){

+        this.sendFeedbackAlert('warning', 'Please do not add a duplicate role name.');

+        return;

+      }

+    }

+  

+      this.groupRoles.push({roleName: this.roleName, readPermission: true, writePermission: false, executePermission: false, deletePermission: false, managementPermission: false});

+      await this.update();

+      this.setComponentData(this._groups.getGroup());

+  

+

+  }

+

+  async update(){

+  

+    

+    for (let i = 0; i < this.groupRoles.length; i++) {

+      this.groupRoles[i].permissions = [];

+      if(this.groupRoles[i].readPermission){

+        this.groupRoles[i].permissions.push('read');

+      }

+      if(this.groupRoles[i].writePermission){

+        this.groupRoles[i].permissions.push('write');

+      }

+      if(this.groupRoles[i].executePermission){

+        this.groupRoles[i].permissions.push('execute');

+      }

+      if(this.groupRoles[i].deletePermission){

+        this.groupRoles[i].permissions.push('delete');

+      }

+      if(this.groupRoles[i].managementPermission){

+        this.groupRoles[i].permissions.push('management');

+      }

+

+    }

+      

+    this.groupPermissions = this.groupRoles.map(({ roleName, permissions }) => ({roleName, permissions}));

+   

+    let groupPatch = {

+      '_id': this.selectedGroup._id,

+      'roles': this.groupPermissions

+    };

+    //console.log(groupPatch);

+    await this._groups.patch(groupPatch).pipe(take(1)).toPromise();

+

+      

+

+  }

+

+  async deleteRole(element){

+

+    for (let i = 0; i < this.groupRoles.length; i++){

+        if (this.groupRoles[i].roleName == element.roleName){

+          this.groupRoles.splice(i, 1);

+          break;

+        }

+    }

+    await this.update();

+    this.setComponentData(this._groups.getGroup());

+

+

+

+

+  }

+

+  public sendFeedbackAlert(type: string, message: string) {

+    this.ResponseMatDialog.open(AlertModalComponent, {

+        width: '250px',

+        data: {

+            type: type,

+            message: message

+        }

+    });

+  }

+

+

+

+

+

+

+

+

+ 

+

+

+

+

+

+

+}

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
index 0000000..f8f1398
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group-routing.module.ts
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {RouterModule, Routes} from '@angular/router';

+import {ManageGroupComponent} from './manage-group.component';

+import { ManageGroupRolesComponent } from './manage-group-roles/manage-group-roles.component';

+

+const routes: Routes = [

+    {

+        path: '',

+        component: ManageGroupComponent

+    },

+    {

+        path: 'manage-group-roles', component: ManageGroupRolesComponent

+      },

+];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class ManageGroupRoutingModule { }

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
index 0000000..e4b7700
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group.component.pug
@@ -0,0 +1,40 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition]).mb-3

+  h2 Manage Group

+

+  .card-mb-12

+    div.mb-1

+      mat-card-title(*ngIf="hasMembers") {{ group.groupName }}

+  

+      button.mr-2.pull-right(matTooltip="Remove user", color="warn", mat-raised-button, (click)="removeMembers()", [disabled] = "!hasSelectedRows") Remove

+      button.mr-2.pull-right(matTooltip="Edit user roles", color="accent", mat-raised-button, (click)="editRoles()", [disabled]="!hasSelectedRows || multipleRowsSelected") Edit Roles

+      button.mr-2.pull-right(mat-raised-button, color="primary", (click)="openUserSelect()") Add Users

+      //button.mr-2.pull-right(mat-raised-button, color="primary", (click)="onboardMechid()") Add Mech Id

+      button.mr-2.pull-right(mat-raised-button, color="primary", [routerLink]="['manage-group-roles']") Manage Group Roles

+     

+      mat-card-content

+        .clearfix

+        ag-grid-angular.ag-theme-material(

+          style="width:100%; height: 600px", 

+          [rowData]="rowData", 

+          [columnDefs]="columnDefs",

+          rowSelection="multiple",

+          [rowMultiSelectWithClick]="true",

+          (cellClicked)="onCellClicked($event)",

+          (rowSelected)="onRowSelected($event)",

+          (gridReady)="onGridReady($event)")

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..dd3ba91
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { ManageGroupComponent } from './manage-group.component';

+

+describe('ManageGroupComponent', () => {

+  let component: ManageGroupComponent;

+  let fixture: ComponentFixture<ManageGroupComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ ManageGroupComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(ManageGroupComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..e6ea356
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group.component.ts
@@ -0,0 +1,234 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { GroupService } from 'app/shared/services/group.service';

+import { UserService } from 'app/shared/services/user.service';

+import { routerTransition } from '../../router.animations';

+import { MatDialog } from '@angular/material';

+import { UserSelectComponent } from 'app/shared/modules/user-select/user-select.component';

+import { MulticastOperator } from 'rxjs/internal/operators/multicast';

+import { forEach } from '@angular/router/src/utils/collection';

+import { OnboardMechidComponent } from 'app/shared/modules/onboard-mechid/onboard-mechid.component';

+import { DropdownMultiselectComponent } from './dropdown-multiselect.component';

+import { TabbedLayout } from 'ag-grid-community';

+import value from '*.json';

+import { take } from 'rxjs/operators';

+import { object } from '@amcharts/amcharts4/core';

+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';

+

+

+@Component({

+  selector: 'app-manage-group',

+  templateUrl: './manage-group.component.pug',

+  styleUrls: ['./manage-group.component.scss'],

+  animations: [routerTransition()]

+})

+export class ManageGroupComponent implements OnInit {

+

+  public group;

+  public loading = false;

+  public users;

+  public tableData; 

+  public hasMembers = false;

+  public hasSelectedRows = false;

+  private gridApi;

+  private gridColumnApi;

+  public memberTable;

+  public rowData;

+  public multipleRowsSelected = false;

+ 

+

+  public columnDefs = [

+    { headerName: 'First Name', field: 'firstName', sortable: true, sort: "asc", filter: true, checkboxSelection: true, headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true},

+    { headerName: 'Last Name', field: 'lastName', sortable: true, filter: true },

+    { headerName: 'Email', field: 'email', sortable: true, filter: true },

+    { headerName: 'Roles', field:'roles', sortable: true, filter: true, editable: true, cellEditor: "DropdownMultiselectComponent", cellEditorParams: function (params) {

+        return {

+            roles: params.roles,

+            userId: params._id

+        };

+    }

+    },

+  ];

+

+  constructor(private groupService: GroupService, private userService: UserService, private modal: MatDialog,) { }

+

+  ngOnInit() {

+    this.group = {};

+    //this.tableData = [];

+    this.group = this.groupService.getGroup();

+   

+    

+    this.groupService.groupChange().subscribe(group => {

+      this.tableData = undefined;

+      this.rowData = undefined;

+      this.setComponentData(group);

+    });

+

+    this.groupService.get(this.group._id).subscribe((res) => {

+      this.group = res;

+      this.setComponentData(this.group);

+    });

+

+    

+  }

+

+  openUserSelect(){

+    this.modal.open(UserSelectComponent, {

+      width: "500px",

+      data: {

+          groupId: this.group._id

+      }

+    }).afterClosed().subscribe((response) => {

+      this.groupService.get(this.group._id).subscribe((res) => {

+        this.group = res;

+        this.setComponentData(this.group);

+      });

+

+    });

+    

+      

+  }

+

+  onboardMechid(){

+    this.modal.open(OnboardMechidComponent, {

+      width: "500px",

+      data: {

+          groupId: this.group._id

+      }

+    }).afterClosed().subscribe((response) => {

+

+    });

+  }

+

+  removeMembers(){

+    let membersToRemove = this.gridApi.getSelectedRows().map(({_id}) => ({_id}));

+    this.group.members = this.group.members.filter(member => membersToRemove.filter(user => member.userId.toString() == user._id.toString()).length <= 0); 

+    let groupPatch = {

+      _id : this.group._id,

+      members: this.group.members

+    }

+    //removes the members from the group

+    this.groupService.patch(groupPatch).subscribe(

+      (res) => {

+        this.gridApi.deselectAll();

+        this.tableData = this.tableData.filter(member => membersToRemove.filter(user => member._id.toString() == user._id.toString()).length <= 0);

+        this.rowData = Object.assign([], this.tableData);

+      }, 

+      (err) => {

+        this.modal.open(AlertModalComponent, {

+          data: {

+            type: "alert",

+            message: "The was an error removing the user. " + err

+          }

+        });

+    });

+    

+  }

+

+  setComponentData(group){

+    this.gridApi.deselectAll();

+    if(!group){

+      return;

+    }

+    this.loading = true;

+    this.group = group;

+    this.users = [];

+    //this.tableData = [];

+    //console.log("Running Data")

+    this.hasMembers = true;

+    this.columnDefs[this.columnDefs.length-1]["cellEditorParams"]["values"] = this.group.roles;

+    if(this.group.members){

+      

+      //console.log(this.group)

+      for(let i = 0; i < this.group.members.length; i++){

+        let temp = this.group.members[i]["userId"];

+        this.userService.get(temp).subscribe(

+        (res) => {

+          let member = res;

+          member["roles"] = this.group.members[i].roles.join();

+          if(!this.tableData){

+            this.tableData = [];

+          }

+          if(this.tableData.filter(user => user['_id'].toString() == member["_id"].toString()).length <= 0){

+            this.tableData.push(member);

+          }else{

+            this.tableData = this.tableData.filter(user => user['_id'].toString() != member["_id"].toString())

+            this.tableData.push(member);

+          }

+         // console.log(this.tableData);

+          this.rowData = Object.assign([], this.tableData);

+        });

+        

+        

+      }

+    }else{

+      this.hasMembers = false;

+    }

+    

+    

+    //need to either populate user or pull each user's info

+    //this.rowData = this.tableData;

+    //console.log(this.rowData);

+  }

+

+  editRoles(){

+    //console.log(this.tableData);

+    this.gridApi.refreshCells();

+    let memberToEdit = this.gridApi.getSelectedRows().map(({_id}) => ({_id}));

+    this.modal.open(DropdownMultiselectComponent, {

+      width: "500px",

+      data : { 

+        user : memberToEdit,

+        group: this.group

+      }

+    }).afterClosed().subscribe((res) => {

+      this.groupService.get(this.group._id).subscribe((res) => {

+        this.group = res;

+        this.setComponentData(this.group);

+      });

+    })

+  }

+

+  onCellClicked(event) {

+    //console.log(event.colDef.field)

+  }

+

+  onRowSelected(event){

+    if(event.api.getSelectedNodes().length > 0){

+      this.hasSelectedRows = true;

+      if(event.api.getSelectedNodes().length > 1){

+        this.multipleRowsSelected = true;

+      }else{

+        this.multipleRowsSelected = false;

+      }

+    }else{

+      this.hasSelectedRows = false;

+      this.multipleRowsSelected = false;

+    }

+  }

+

+  onGridReady(params){

+    this.gridApi = params.api;

+    //console.log(params.columnApi.autoSizeColumns)

+    this.gridColumnApi = params.columnApi;

+

+    //auto size the column widths

+    this.gridColumnApi.autoSizeColumns(['name']);

+  }

+

+}

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
index 0000000..1c86cf2
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { ManageGroupModule } from './manage-group.module';

+

+describe('ManageGroupModule', () => {

+  let manageGroupModule: ManageGroupModule;

+

+  beforeEach(() => {

+    manageGroupModule = new ManageGroupModule();

+  });

+

+  it('should create an instance', () => {

+    expect(manageGroupModule).toBeTruthy();

+  });

+});

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
index 0000000..b0c4580
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/manage-group/manage-group.module.ts
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { FormsModule, ReactiveFormsModule } from '@angular/forms';

+import { ManageGroupComponent } from './manage-group.component';

+import { ManageGroupRoutingModule } from './manage-group-routing.module';

+import { AgGridModule } from 'ag-grid-angular';

+import { UserSelectModule } from 'app/shared/modules/user-select/user-select.module';

+import { DropdownMultiselectComponent } from './dropdown-multiselect.component';

+import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';

+import { MatProgressSpinnerModule, MatIconModule, MatCardModule, MatCheckboxModule, MatTableModule, MatButtonModule, MatInputModule, MatFormFieldModule, MatTooltipModule} from '@angular/material';

+import { ManageGroupRolesComponent } from './manage-group-roles/manage-group-roles.component';

+import {PageHeaderModule} from '../../shared';

+import {AlertModalModule} from 'app/shared/modules/alert-modal/alert-modal.module';

+import { OnboardMechidModule } from 'app/shared/modules/onboard-mechid/onboard-mechid.module';

+

+

+@NgModule({

+  imports: [

+    CommonModule,

+    MatCardModule,

+    ManageGroupRoutingModule,

+    AgGridModule.withComponents([DropdownMultiselectComponent]),

+    NgbDropdownModule,

+    UserSelectModule,

+    OnboardMechidModule,

+    MatCheckboxModule, 

+    MatTableModule,

+    MatInputModule,

+    MatFormFieldModule,

+    MatTooltipModule,

+    PageHeaderModule,

+    FormsModule,

+    MatButtonModule,

+    MatIconModule,

+    MatProgressSpinnerModule,

+    ReactiveFormsModule,

+    AlertModalModule

+  

+  ],

+  declarations: [ManageGroupComponent, DropdownMultiselectComponent, ManageGroupRolesComponent]

+    

+  

+})

+export class ManageGroupModule { }

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
index 0000000..3495cdd
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/color-picker/ColorPicker.js
@@ -0,0 +1,80 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {

+    is

+  } from 'bpmn-js/lib/util/ModelUtil';

+  

+  

+  /**

+   * A basic color picker implementation.

+   *

+   * @param {EventBus} eventBus

+   * @param {ContextPad} contextPad

+   * @param {CommandStack} commandStack

+   */

+  export default function ColorPicker(eventBus, contextPad, commandStack) {

+  

+    contextPad.registerProvider(this);

+  

+    commandStack.registerHandler('shape.updateColor', UpdateColorHandler);

+  

+    function changeColor(event, element) {

+  

+      var color = window.prompt('type a color code');

+  

+      commandStack.execute('shape.updateColor', { element: element, color: color });

+    }

+  

+  

+    this.getContextPadEntries = function(element) {

+  

+      if (is(element, 'bpmn:Event')) {

+        return {

+          'changeColor': {

+            group: 'edit',

+            className: 'icon-red',

+            title: 'Change element color',

+            action: {

+              click: changeColor

+            }

+          }

+        };

+      }

+    };

+  }

+  

+  

+  

+  /**

+   * A handler updating an elements color.

+   */

+  function UpdateColorHandler() {

+  

+    this.execute = function(context) {

+      context.oldColor = context.element.color;

+      context.element.color = context.color;

+  

+      return context.element;

+    };

+  

+    this.revert = function(context) {

+      context.element.color = context.oldColor;

+  

+      return context.element;

+    };

+  

+  }
\ 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
index 0000000..957e8b9
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/color-picker/ColoredRenderer.js
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import inherits from 'inherits';

+

+import {

+  attr as svgAttr

+} from 'tiny-svg';

+

+import BpmnRenderer from 'bpmn-js/lib/draw/BpmnRenderer';

+

+import {

+  is

+} from 'bpmn-js/lib/util/ModelUtil';

+

+

+export default function ColoredRenderer(

+    config, eventBus, styles,

+    pathMap, canvas, textRenderer) {

+

+  BpmnRenderer.call(

+    this,

+    config, eventBus, styles,

+    pathMap, canvas, textRenderer,

+    1400

+  );

+

+  this.canRender = function(element) {

+    return is(element, 'bpmn:BaseElement') && element.color;

+  };

+

+  this.drawShape = function(parent, shape) {

+

+    var bpmnShape = this.drawBpmnShape(parent, shape);

+

+    svgAttr(bpmnShape, { fill: shape.color });

+

+    return bpmnShape;

+  };

+}

+

+inherits(ColoredRenderer, BpmnRenderer);

+

+ColoredRenderer.prototype.drawBpmnShape = BpmnRenderer.prototype.drawShape;

+

+

+ColoredRenderer.$inject = [

+  'config.bpmnRenderer',

+  'eventBus',

+  'styles',

+  'pathMap',

+  'canvas',

+  'textRenderer'

+];
\ 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
index 0000000..049e921
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/color-picker/index.js
@@ -0,0 +1,24 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import ColorPicker from './ColorPicker';

+import ColoredRenderer from './ColoredRenderer';

+

+export default {

+  __init__: [ 'colorPicker', 'coloredRenderer' ],

+  colorPicker: [ 'type', ColorPicker ],

+  coloredRenderer: [ 'type', ColoredRenderer ]

+};
\ 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
index 0000000..607cd08
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/draw/LogTestResultRenderer.js
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import inherits from 'inherits';

+

+import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';

+

+import {

+  is

+} from 'bpmn-js/lib/util/ModelUtil';

+

+import Cat from '../image';

+

+import {

+  append as svgAppend,

+  create as svgCreate

+} from 'tiny-svg';

+

+

+export default function NyanRender(eventBus) {

+  BaseRenderer.call(this, eventBus, 1500);

+

+  this.canRender = function(element) {

+    return is(element, 'custom:Log');

+  };

+

+

+  this.drawShape = function(parent, shape) {

+    var url = Cat.dataURL;

+

+    var catGfx = svgCreate('image', {

+      x: 0,

+      y: 0,

+      width: shape.width,

+      height: shape.height,

+      href: url

+    });

+

+    svgAppend(parent, catGfx);

+

+    return catGfx;

+  };

+}

+

+inherits(NyanRender, BaseRenderer);

+

+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
index 0000000..a73db29
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/draw/index.js
@@ -0,0 +1,22 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import LogTestResultRenderer from './LogTestResultRenderer';

+

+export default {

+  __init__: [ 'logTestResultRenderer' ],

+  logTestResultRenderer: [ 'type', LogTestResultRenderer ]

+};
\ 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
index 0000000..bf0314f
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/cat.gif
Binary files 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
index 0000000..29fce79
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/index.js
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// inlined and base64 encoded ./cat.gif

+

+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
index 0000000..7254c21
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/palette/LogTestResultPaletteProvider.js
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import Cat from '../image';

+

+

+/**

+ * A provider for quick service task production

+ */

+export default function LogTestResultPaletteProvider(palette, create, elementFactory) {

+

+  this._create = create;

+  this._elementFactory = elementFactory;

+

+  palette.registerProvider(this);

+}

+

+LogTestResultPaletteProvider.$inject = [

+  'palette',

+  'create',

+  'elementFactory'

+];

+

+LogTestResultPaletteProvider.prototype.getPaletteEntries = function() {

+

+  var elementFactory = this._elementFactory,

+      create = this._create;

+

+  function startCreate(event) {

+    var serviceTaskShape = elementFactory.create(

+      'shape', { type: 'custom:Log' }

+    );

+

+    create.start(event, serviceTaskShape);

+  }

+

+  return {

+    'create-task': {

+      group: 'activity',

+      title: 'Create a new nyan CAT!',

+      imageUrl: Cat.dataURL,

+      action: {

+        dragstart: startCreate,

+        click: startCreate

+      }

+    }

+  };

+};
\ 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
index 0000000..205eb71
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/palette/index.js
@@ -0,0 +1,22 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import LogTestResultPaletteProvider from './LogTestResultPaletteProvider';

+

+export default {

+  __init__: [ 'logTestResultPaletteProvider' ],

+  logTestResultPaletteProvider: [ 'type', LogTestResultPaletteProvider ]

+};
\ 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
index 0000000..5439019
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/modeler-routing.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { ModelerComponent } from './modeler.component';

+

+const routes: Routes = [

+  {

+    path: '', component: ModelerComponent

+  }

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class ModelerRoutingModule { }

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
index 0000000..a62cd9d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/modeler.component.pug
@@ -0,0 +1,162 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div(#container, style="width:100%;height:100%")

+	form(#testDefinitionForm="ngForm")

+

+		//- Tool Bar

+		div#tool-bar

+			input#file(type="file", #file, hidden, (change)="open()")

+			input#fileForVersion(type="file", #fileForVersion, hidden, (change)="newVersion({fromFile: true})")

+			.row.pl-2

+			

+				//- BPMN Diagram items	

+

+				.col(style="flex: 0 0 180px;-ms-flex: 0 0 180px;")

+					.row.no-margin.pl-1

+						.small.text-muted BPMN Diagram

+					.row.p-0.pl-2(style="margin-top: -10px")

+						button(mat-icon-button, [matMenuTriggerFor]="newMenu")

+							mat-icon insert_drive_file 

+								span(style="margin-left: -7px") arrow_drop_down

+						mat-menu(#newMenu="matMenu")

+							button.text-small(mat-menu-item, (click)="newWorkflow()") New Workflow

+						button(mat-icon-button, matTooltip="Open BPMN", (click)="file.click()")

+							mat-icon folder_open

+						button(mat-icon-button, matTooltip="Download BPMN", (click)="download()")

+							mat-icon cloud_download

+						button(mat-icon-button, matTooltip="View XML", disabled)

+							mat-icon code

+						

+

+				mat-divider([vertical]="true")

+

+				//- Test Definition items

+

+				.col-3()

+					.row.no-margin.pl-1

+						.small.text-muted Test Definition

+					.row.p-0.pl-2(style="margin-top: -10px")

+						//- Save & Update

+						button(mat-icon-button, matTooltip="Save Test Definition", [disabled]="inProgress || !testDefinitionForm.form.valid || (hasBeenSaved && !testDefinitionForm.dirty)", (click)="save()")

+							mat-icon save

+						//- Deploy

+						button(mat-icon-button, matTooltip="Deploy Test Definition", [disabled]="!testDefinitionForm.form.valid || ptd?.currentInstance?.isDeployed || !hasBeenSaved || !testDefinitionForm.pristine || inProgress", (click)="deploy()")

+							mat-icon cloud_upload

+						//- Delete Version

+						button(mat-icon-button, matTooltip="Delete Version", [disabled]="!hasBeenSaved || inProgress", (click)="deleteVersion()")

+							mat-icon delete_forever

+

+						//- Version Select

+						mat-form-field(*ngIf="ptd", style="width: 80px")

+							mat-select(disableOptionCentering, (selectionChange)="setVersion(ptd.currentVersionName)", placeholder="Version", name="selectedVersion", [(ngModel)]="ptd.currentVersionName")

+								mat-option(*ngFor="let instance of ptd.bpmnInstances.slice().reverse()", value="{{instance.version}}") {{ instance.version }}

+						button(mat-icon-button, [matMenuTriggerFor]="versionMenu", matTooltip="New Version")

+							mat-icon add

+						mat-menu(#versionMenu="matMenu")

+							button(mat-menu-item, [matMenuTriggerFor]="fromVersion") Create from version

+							button(mat-menu-item, (click)="newVersion()") Create blank version

+							button(mat-menu-item, (click)="fileForVersion.click()") Create from file

+						mat-menu(#fromVersion="matMenu")

+							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 }}

+

+					

+		div#left_panel(#modeler)

+			.panel-buttons

+				.properties-panel-button((click)="toggleProperties()") Properties Panel

+				.properties-panel-button((click)="toggleTestDefinition()") Test Definition

+		div.properties-panel(#sidebar, [hidden]="!showSidebar")

+			div#properties(#properties, [hidden]="!showProperties", style="width:100%")

+			div(#testDefinition, *ngIf="ptd", [hidden]="!showTestDefinition", style="width:100%;")

+				.col-12

+					.row.mt-2

+						.col-12

+							

+							//- Test Definition Form

+

+							h4 Details

+

+							mat-form-field(style="width:100%")

+								input(matInput, type="text", placeholder="Name", name="name", [disabled]="(existingTd && !hasBeenSaved)", [(ngModel)]="ptd.testName", required)

+								mat-error Required

+							mat-form-field(style="width:100%")

+								input(matInput, type="text", placeholder="Description", name="description", [disabled]="(existingTd && !hasBeenSaved)", [(ngModel)]="ptd.testDescription", required)

+								mat-error Required

+							mat-form-field(style="width:100%")

+								mat-select((selectionChange)="markFormAs('dirty')", name="ns", [disabled]="(existingTd && !hasBeenSaved)", placeholder="Group", [(value)]="ptd.groupId", required)

+									mat-option(*ngFor="let group of groups", value="{{group._id}}") {{ group.groupName }}

+								mat-error Required

+							//- mat-form-field(style="width:100%")

+							//- 	input(matInput, type="text", *ngIf="!hasBeenSaved", placeholder="Version", name="version", [(ngModel)]="ptd.currentInstance.version", (keyup)="checkVersionUnique()", required)

+							//- 	mat-select((selectionChange)="switchVersion(ptd.currentVersionName)", placeholder="Version", name="selectedVersion", *ngIf="hasBeenSaved", [(value)]="ptd.currentVersionName", required)

+							//- 		mat-option(*ngFor="let instance of ptd.bpmnInstances", value="{{instance.version}}") {{ instance.version }}

+							//- 	mat-error Required

+							//- 	button(mat-button, matSuffix, color="primary", *ngIf="hasBeenSaved", (click)="newVersion(this.ptd.processDefinitionKey)", onclick="file.click();") New

+

+					//- .row.mt-2

+					//- 	.col-12

+					//- 		h4 Resources

+					//- 		.text-muted A single .zip file with scripts

+					//- input(type="file", #scripts, id="scripts", name="scripts", hidden,  (change)="markFormAs('dirty')", ng2FileSelect, [uploader]="uploader", accept="application/zip")

+					//- .row.mt-1(*ngIf="ptd.currentInstance.resourceFileId")

+					//- 	.col-12

+					//- 		mat-list

+					//- 			mat-list-item

+					//- 				mat-icon(mat-list-icon) insert_drive_file

+					//- 				h4(mat-line) {{ptd.currentInstance.resourceFileName }}

+					//- .row(*ngIf="!ptd.currentInstance.isDeployed")

+					//- 	.col-md-12

+					//- 		button(mat-raised-button, onclick="scripts.click()", color="primary") 

+					//- 			| {{ !ptd.currentInstance.resourceFileId ? 'Choose File' : 'Replace File' }}

+							

+					//- 	.col-md-12

+					//- 		div(*ngIf="uploader?.queue.length > 0")

+					//- 			label File:

+					//- 			ul.list-group(style="position:relative")

+					//- 				li.list-group-item(*ngFor="let item of uploader.queue")

+					//- 					| {{ item?.file?.name }}

+					//- 					div.upload-progress([ngStyle]="{'width': item.progress + '%'}")

+					//- 			//- button.pull-right(mat-button, (click)="upload()") Upload All

+					//- 			label(*ngIf="ptd.currentInstance.resourceFileId && uploader.queue.length > 0 && !saved") This will replace the previous resouce file

+					//- 			button.pull-right(mat-button, color="primary", (click)="clearQueue()") Remove All

+					//- .row(*ngIf="ptd.currentInstance.isDeployed")

+					//- 	.col-12(*ngIf="!ptd.currentInstance.resourceFileId")

+					//- 		| No resources were deployed with this version

+					

+					.row.mt-3

+						.col-12(*ngIf="ptd.currentInstance.testDataTemplate != {}")

+							h4 testInputTemplate.yaml

+							div(style="border: 1px solid lightgrey; font-size: 12px !important")

+								codemirror(*ngIf="isRefreshed", [config]="codeConfig", [(ngModel)]="ptd.currentInstance.testDataTemplate", name="testConfig")

+

+

+			#drag(#handle)

+

+div(style="position:absolute; bottom: 5px; left: 5px")

+	div(*ngIf="inProgress")

+		mat-spinner([diameter]="15", style="display:inline")

+		div.ml-4(style="display:inline") In Progress

+	div(*ngIf="ptd?.currentInstance?.isDeployed") Deployed

+	div(*ngIf="hasBeenSaved && !testDefinitionForm.dirty") saved

+		mat-icon(style="color:green") check

+	

+	//- div Form valid: {{ form.valid }}

+	//- div Form dirty: {{ form.dirty }}

+	//- div hasBeenSaved: {{ hasBeenSaved }}

+	//- div(*ngIf="ptd?.currentInstance?.bpmnHasChanged") ptd.currentInstance.bpmnHasChanged

+	//- 

+	//- button((click)="popup()") popup

+	//- button((click)="nav()") nav

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
index 0000000..61cbddc
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/modeler.component.scss
@@ -0,0 +1,97 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.properties-panel {

+  position: absolute;

+  top: 0;

+  bottom: 0;

+  right: 0;

+  width: 300px;

+  z-index: 10;

+  border-left: 1px solid #ccc;

+  overflow: auto;

+  background: white;

+}

+

+.properties-panel:empty {

+  display: none;

+}

+

+.properties-panel > .djs-properties-panel {

+  padding-bottom: 70px;

+  min-height: 100%;

+}

+

+.properties-toggle {

+  position: absolute;

+  top: 0;

+  right: 280px;

+}

+

+#left_panel {

+  position: absolute;

+  left: 0;

+  top: 40px;

+  bottom: 0;

+  right: 300px;

+}

+

+#drag {

+  position: absolute;

+  left: -4px;

+  top: 0;

+  bottom: 0;

+  width: 15px;

+  cursor: w-resize;

+  z-index: 1000;

+}

+

+.panel-buttons {

+  position:absolute; 

+  right: -103px; 

+  z-index: 5; 

+  top: 30%; 

+  transform: rotate(270deg);

+}

+

+.properties-panel-button {

+  cursor: pointer;

+  padding: 5px 10px;

+  user-select: none;

+  background: lightgrey;

+  margin-right: 10px;

+  display: inline;

+}

+

+#tool-bar {

+  position: absolute;

+  top: 0px;

+  left: 0px;

+  width: 100%;

+  background: #f8f8f8;

+}

+

+#tool-bar button mat-icon {

+  font-size: 18px;

+}

+

+#tool-bar button {

+  line-height: 30px !important;

+}

+

+.no-margin{

+  margin: 0px !important;

+}
\ 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
index 0000000..0c62b8f
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/modeler.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { ModelerComponent } from './modeler.component';

+

+describe('ModelerComponent', () => {

+  let component: ModelerComponent;

+  let fixture: ComponentFixture<ModelerComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ ModelerComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(ModelerComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..c090769
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/modeler.component.ts
@@ -0,0 +1,821 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, HostListener } from '@angular/core';

+import minimapModule from 'diagram-js-minimap';

+import { FileTransferService } from 'app/shared/services/file-transfer.service';

+import { Buffer } from 'buffer';

+import propertiesPanelModule from 'bpmn-js-properties-panel';

+import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';

+import * as camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json';

+import * as vthTemplate from './templates/elements.json';

+import * as $ from 'jquery';

+import { MatDialog, MatSnackBar } from '@angular/material';

+import { TestHeadService } from 'app/shared/services/test-head.service';

+import { GroupService } from 'app/shared/services/group.service';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+import { CookieService } from 'ngx-cookie-service';

+import { FileService } from 'app/shared/services/file.service';

+import { ActivatedRoute, Router } from '@angular/router';

+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';

+import { Bpmn } from 'app/shared/models/bpmn.model';

+import { TestDefinitionElement, BpmnInstanceElement } from './test-definition-element.class.js';

+import { FileUploader } from 'ng2-file-upload';

+import { Group } from 'app/shared/models/group.model.js';

+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';

+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';

+

+interface NewVersionOptions {

+  versionIndex: number,

+  fromFile: Boolean

+}

+

+@Component({

+  selector: 'app-modeler',

+  templateUrl: './modeler.component.pug',

+  styleUrls: [

+    './modeler.component.scss',

+  ]

+})

+

+export class ModelerComponent implements OnInit {

+

+  @ViewChild('container') containerElement: ElementRef;

+  @ViewChild('modeler') modelerElement: ElementRef;

+  @ViewChild('sidebar') sidebarElement: ElementRef;

+  @ViewChild('properties') propertiesElement: ElementRef;

+  @ViewChild('handle') handleElement: ElementRef;

+

+  @ViewChild('testDefinitionForm') form: any;

+  @ViewChild('scripts') scripts: ElementRef;

+  @ViewChild('file') bpmnFileInput: ElementRef;

+

+  public qpTestDefinitionId;

+

+  public ptd: TestDefinitionElement;

+  public uploader: FileUploader;

+  public bpmnUploader: FileUploader;

+  public pStatus: String;

+  public inProgress: Boolean;

+  public groups: Array<Group>;

+  public modeler: Bpmn;

+  public showProperties = true;

+  public isResizing = false;

+  public lastDownX = 0;

+  public propertiesWidth = '500px';

+  public showSidebar = true;

+  public showTestDefinition = false;

+  public bpmnId; //javascript input element

+  public isRefreshed = false;

+  public hasBeenSaved: Boolean = false;

+

+  constructor(

+    public _dialog: MatDialog,

+    private _testHeads: TestHeadService,

+    private _groups: GroupService,

+    private _testDefinitions: TestDefinitionService,

+    private _snack: MatSnackBar,

+    private _fileTransfer: FileTransferService,

+    private _route: ActivatedRoute,

+    private _router: Router,

+    private _bpmnFactory: BpmnFactoryService) {

+  }

+

+  @HostListener('window:beforeunload', ['$event'])

+  canLeavePage($event) {

+    $event.preventDefault();

+    alert('are you sure')

+  }

+

+  async ngOnInit() {

+

+    this._route.queryParams.subscribe(res => {

+      if (res.testDefinitionId) {

+        this.qpTestDefinitionId = res.testDefinitionId;

+      } else {

+        this.qpTestDefinitionId = null;

+      }

+      this.setup();

+    })

+

+    //set groups list

+    this._groups.find({

+      $limit: -1,

+      lookup: 'both'

+    }).subscribe(res => {

+      this.groups = res as Array<Group>;

+      this.groups = this._groups.organizeGroups(this.groups);

+      

+    });

+

+  }

+

+  async setup() {

+

+    this.setInProgress(true);

+

+    await this.setTestDefinition();

+

+    const modelerOptions = {

+      container: this.modelerElement.nativeElement,

+      propertiesPanel: {

+        parent: '#properties'

+      },

+      elementTemplates: [vthTemplate],

+      additionalModules: [

+        minimapModule,

+        propertiesPanelModule,

+        propertiesProviderModule

+        // colorPickerModule,

+        // logTestResultDrawModule,

+        // logTestResultPaletteModule

+      ],

+      moddleExtensions: {

+        camunda: camundaModdleDescriptor

+      },

+      keyboard: {

+        bindTo: document

+      }

+    };

+

+    // Set up empty modeler

+    await this.setModeler({

+      mode: 'modeler',

+      options: modelerOptions

+    });

+

+    this.setBpmn(false);

+

+    //set ups draggable properties container

+    $(this.handleElement.nativeElement).on('mousedown', e => {

+      this.lastDownX = e.clientX;

+      this.isResizing = true;

+    });

+

+    $(document).on('mousemove', e => {

+      if (!this.isResizing)

+        return;

+

+      var offsetRight = $(this.containerElement.nativeElement).width() - (e.clientX - $(this.containerElement.nativeElement).offset().left);

+

+      $(this.modelerElement.nativeElement).css('right', offsetRight);

+      $(this.sidebarElement.nativeElement).css('width', offsetRight);

+    }).on('mouseup', e => {

+      this.isResizing = false;

+    });

+

+

+  }

+

+  /*****************************************

+   * Form Functionality Methods

+   ****************************************/

+

+  /*** BUTTONS ***/

+

+  async newWorkflow() {

+    if (this.qpTestDefinitionId) {

+      this._router.navigate([], {

+        queryParams: {}

+      });

+    } else {

+      this.setup();

+    }

+  }

+

+  async download() {

+    this.modeler.download();

+  }

+

+  async save() {

+    this.setInProgress(true);

+    let validResult = await this.validateFile();

+

+    if (validResult) {

+      if (this.hasBeenSaved) {

+        await this.updateDefinition();

+      } else {

+        let td = await this.saveDefinition();

+        this._router.navigate([], {

+          queryParams: {

+            testDefinitionId: td['_id']

+          }

+        });

+      }

+    }

+

+    this.snackAlert('Version ' + this.ptd.currentVersionName + ' has been saved');

+    this.setInProgress(false);

+    this.markFormAs('pristine');

+  }

+

+  async deploy(versionName?) {

+    this.inProgress = true;

+

+    this._testDefinitions.deploy(this.ptd, versionName)

+      .subscribe(

+        result => {

+          this.inProgress = false;

+          if (result['statusCode'] == 200) {

+            this.snackAlert('Test Definition Deployed Successfully')

+            this.ptd.currentInstance.isDeployed = true;

+          } else {

+            this.errorPopup(result.toString());

+          }

+        },

+        err => {

+          this.errorPopup(err.toString());

+          this.setInProgress(false);

+        }

+

+      );

+  }

+

+  async deleteVersion() {

+    let deleteDialog = this._dialog.open(AlertModalComponent, {

+      width: '250px',

+      data: { type: 'confirmation', message: 'Are you sure you want to delete version ' + this.ptd.currentVersionName }

+    });

+

+    deleteDialog.afterClosed().subscribe(

+      result => {

+        if (result) {

+          this.setInProgress(true);

+          if (this.ptd.bpmnInstances.length == 1) {

+            this._testDefinitions.delete(this.ptd._id).subscribe(

+              result => {

+                this.snackAlert('Test definition was deleted');

+                this.setInProgress(false);

+                this.newWorkflow();

+              },

+              err => {

+                this.setInProgress(false);

+                this.errorPopup(err.toString());

+              }

+            )

+          } else {

+            let version = this.ptd.currentVersionName;

+            // if deleting a version from a definition that has more than 1 version

+            this.ptd.removeBpmnInstance(this.ptd.currentVersionName);

+

+            //prepare patch request

+            let request = {

+              _id: this.ptd._id,

+              bpmnInstances: this.ptd.bpmnInstances

+            }

+

+            this._testDefinitions.patch(request).subscribe(

+              res => {

+                this.setVersion();

+                this.setInProgress(false);

+                this.snackAlert('Version ' + version + ' was deleted');

+              },

+              err => {

+                this.setInProgress(false);

+                this.errorPopup(err.toString());

+              }

+            );

+          }

+        }

+      }

+    )

+  }

+

+

+  /*** UTILITY METHODS ***/

+

+  //Looks for the definition supplied in the url, or pulls up default workflow

+  async setTestDefinition() {

+    return new Promise((resolve, reject) => {

+      if (this.qpTestDefinitionId) {

+        this._testDefinitions.get(this.qpTestDefinitionId).subscribe(

+          result => {

+            

+            this.ptd = new TestDefinitionElement();

+            this.ptd.setAll(result);

+            this.setAsSaved(true);

+            resolve(this.ptd);

+          },

+          err => {

+            this.errorPopup(err.toString());

+            reject(err);

+          }

+        )

+      } else {

+        //set new test definition

+        this.ptd = new TestDefinitionElement();

+        resolve(this.ptd);

+      }

+    });

+

+  }

+

+  //will set the selected version. If no version is given, the latest will be selected

+  async setVersion(version?) {

+

+    //if version not supplied, grab latest

+    this.ptd.switchVersion(version);

+

+    this.setBpmn(true);

+

+  }

+

+

+

+  async newVersion(options?: NewVersionOptions) {

+

+    if (options && options.versionIndex != null) {

+

+      //create new instance and copy xml

+      let instance = this.ptd.newInstance();

+      instance.bpmnFileId = this.ptd.bpmnInstances[options.versionIndex].bpmnFileId;

+      instance.bpmnXml = this.ptd.bpmnInstances[options.versionIndex].bpmnXml;

+

+      this.ptd.addBpmnInstance(instance);

+

+    } else if ( options && options.fromFile) {

+      

+      let instance = this.ptd.newInstance();

+

+      instance.bpmnFileId = '0';

+      let xml = await new Promise((resolve, reject) => {

+        this.fetchFileContents('fileForVersion', xml => {

+          resolve(xml);

+        });

+      });

+

+      instance.bpmnXml = xml as String;

+

+      //set the files process definition key

+      let parser = new DOMParser();

+      let xmlDoc = parser.parseFromString(instance.bpmnXml.toString(), "text/xml");

+      //set the process definition key in xml

+      xmlDoc.getElementsByTagName("bpmn:process")[0].attributes.getNamedItem("id").value = this.ptd.processDefinitionKey as string;

+      //save the xml

+      instance.bpmnXml = (new XMLSerializer()).serializeToString(xmlDoc);

+

+      this.ptd.addBpmnInstance(instance);

+

+    } else {

+      this.ptd.addBpmnInstance();

+    }

+

+    this.setVersion();

+    this.markFormAs('dirty');

+    this.ptd.currentInstance.bpmnHasChanged = true;

+  }

+

+  popup() {

+    

+  }

+

+  async validateFile() {

+    return new Promise((resolve, reject) => {

+

+      this.modeler.getBpmnXml().then(xml => {

+

+        this.ptd.currentInstance.bpmnXml = xml.toString();

+

+        this._testDefinitions.validate(this.ptd)

+          .subscribe(

+            result => {

+

+              if (result['body'].errors && result['body'].errors != {}) {

+                this.errorPopup(JSON.stringify(result['body'].errors));

+                resolve(false);

+              }

+              //this.handleResponse(result, false);

+              //this.ptd.currentInstance.bpmnHasChanged = true;

+

+              // If any VTH or PFLOs were detected, add to object

+              // Update list of test heads

+              if (result['body']['bpmnVthTaskIds']) {

+                this.ptd.currentInstance.testHeads = result['body'].bpmnVthTaskIds;

+                this.ptd.currentInstance.testHeads.forEach((elem, val) => {

+                  this.ptd.currentInstance.testHeads[val]['testHeadId'] = elem['testHead']._id;

+                  delete this.ptd.currentInstance.testHeads[val]['testHead'];

+                })

+

+              }

+

+              //Update plfos list

+              if(result['body']['bpmnPfloTaskIds']){

+                this.ptd.currentInstance.pflos = result['body'].bpmnPfloTaskIds;

+              }

+

+              resolve(true);

+            },

+            err => {

+              reject(false);

+            }

+          );

+

+      }).catch(err => {

+        this.errorPopup(err);

+      });

+

+    });

+

+  }

+

+  //returns promise for file object 

+  async saveBpmnFile() {

+    return new Promise((resolve, reject) => {

+

+      this.modeler.getBpmnXml().then(

+        res => {

+          this.ptd.currentInstance.bpmnXml = res as String;

+          this._testDefinitions.validateSave(this.ptd).subscribe(

+            result => {

+              resolve(JSON.parse(result.toString())[0]._id);

+            },

+            err => {

+              this.errorPopup(err.toString());

+              reject(err);

+            }

+          )

+        }

+      )

+    });

+  }

+

+  async saveDefinition() {

+

+    return new Promise(async (resolve, reject) => {

+      

+      let fileId = await this.saveBpmnFile();

+

+      if (fileId) {

+        this.ptd.currentInstance.bpmnFileId = fileId as String;

+      }

+

+      delete this.ptd._id;

+      

+      this._testDefinitions.create(this.ptd).subscribe(

+        res => {

+          

+          resolve(res);

+        },

+        err => {

+          this.errorPopup(err.message);

+          this.setInProgress(false);

+          reject(err);

+        }

+      )

+    });

+

+  }

+

+  async updateDefinition() {

+    return new Promise(async (resolve, reject) => {

+

+      let versionIndex = this.ptd.currentVersion;

+

+      // set parameters to be sent with the patch

+      let request = {

+        _id: this.ptd._id,

+        testName: this.ptd.testName,

+        testDescription: this.ptd.testDescription,

+        groupId: this.ptd.groupId

+      }

+

+      // If xml has changed, upload file and patch definition details, else just updated details

+      if (this.ptd.currentInstance.bpmnHasChanged) {

+

+        //upload file

+        let fileId = await this.saveBpmnFile();

+

+        //set file id in the bpmn instance

+        if (fileId) {

+          this.ptd.currentInstance.bpmnFileId = fileId as String;

+        }

+      }

+

+      //check if this bpmn version has been saved, else its a new version

+      if (this.ptd.currentInstance.createdAt) {

+        this.ptd.currentInstance.updatedAt = new Date().toISOString();

+        request['bpmnInstances.' + this.ptd.currentVersion] = this.ptd.currentInstance;

+      } else {

+        this.ptd.currentInstance.createdAt = new Date().toISOString();

+        this.ptd.currentInstance.updatedAt = new Date().toISOString();

+        request['$push'] = {

+          bpmnInstances: this.ptd.currentInstance

+        }

+      }

+

+      //patch with updated fields

+      this._testDefinitions.patch(request).subscribe(res => {

+        this.ptd.currentInstance.bpmnHasChanged = false;

+        resolve(res);

+      },

+        err => {

+          reject(err);

+        });

+    });

+  }

+

+  markFormAs(mode: 'dirty' | 'pristine') {

+    if (mode == 'dirty') {

+      this.form.control.markAsDirty();

+    } else {

+      this.form.control.markAsPristine();

+    }

+  }

+

+

+  async checkProcessDefinitionKey() {

+    let foundDefinition = null;

+    

+    this._testDefinitions.check(this.ptd.processDefinitionKey).subscribe(async result => {

+      if (result['statusCode'] == 200) {

+        this.pStatus = 'unique';

+      } else {

+        this.pStatus = 'notUnique';

+      }

+      

+

+      //If process definition key found

+      if (result['body'] && result['body'][0]) {

+

+        foundDefinition = result['body'][0];

+

+      } else {

+        //seach mongodb for td with pdk

+        await new Promise((resolve, reject) => {

+          this._testDefinitions.find({

+            processDefinitionKey: this.ptd.processDefinitionKey

+          }).subscribe(res => {

+            

+            if (res['total'] > 0) {

+              foundDefinition = res['data'][0];

+            }

+            resolve()

+          }, err => {

+            reject();

+          })

+        });

+      }

+      

+      if (foundDefinition) {

+        if (this.qpTestDefinitionId != foundDefinition._id) {

+          let confirm = this._dialog.open(AlertModalComponent, {

+            width: '400px',

+            data: {

+              type: 'confirmation',

+              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.'

+            }

+          });

+

+          confirm.afterClosed().subscribe(doChange => {

+            if (doChange) {

+              this._router.navigate([], {

+                queryParams: {

+                  testDefinitionId: foundDefinition._id

+                }

+              })

+            } else {

+              this.bpmnId.value = '';

+            }

+          })

+        }

+      } else {

+        let tempPK = this.ptd.processDefinitionKey;

+        this.ptd.reset();

+

+        this.ptd.setProcessDefinitionKey(tempPK);

+

+        this.ptd.setId(null);

+        this.ptd.setName('');

+        this.ptd.setDescription('');

+        this.ptd.setGroupId('');

+        this.ptd.setVersion(1);

+        this.setAsSaved(false);

+      }

+

+      if (!this.ptd.currentInstance.version) {

+        this.ptd.setNewVersion();

+      }

+

+      this.markFormAs('pristine');

+

+      this.ptd.currentInstance.bpmnHasChanged = false;

+

+

+    });

+  }

+

+  setInProgress(mode: Boolean) {

+    this.inProgress = mode;

+  }

+

+  setAsSaved(mode: Boolean) {

+    this.hasBeenSaved = mode;

+  }

+

+  /*****************************************

+   * BPMN Modeler Functions

+   ****************************************/

+

+  async setBpmn(isNewVersion: Boolean, xml?) {

+

+    //If a test definition is loaded set bpmnXml with latest version, else set default flow

+    if (xml) {

+      this.ptd.currentInstance.bpmnXml = xml;

+    } else {

+      if (this.ptd._id && this.ptd.currentInstance.bpmnFileId) {

+        if (!this.ptd.currentInstance.bpmnXml) {

+          this.ptd.currentInstance.bpmnXml = await this.getVersionBpmn() as String;

+        }

+      } else {

+        this.ptd.currentInstance.bpmnXml = await this.getDefaultFlow() as String;

+

+        // If it is a blank new version, set the process definition key in xml

+        if (isNewVersion) {

+          let parser = new DOMParser();

+          //Parse xml

+          let xmlDoc = parser.parseFromString(this.ptd.currentInstance.bpmnXml.toString(), "text/xml");

+          //set the process definition key in xml

+          xmlDoc.getElementsByTagName("bpmn:process")[0].attributes.getNamedItem("id").value = this.ptd.processDefinitionKey as string;

+          //save the xml

+          this.ptd.currentInstance.bpmnXml = (new XMLSerializer()).serializeToString(xmlDoc);

+

+        }

+      }

+    }

+

+    await this.modeler.setBpmnXml(this.ptd.currentInstance.bpmnXml);

+

+    //Set bpmn id

+    this.bpmnId = (<HTMLInputElement>document.getElementById("camunda-id"));

+

+    if (!isNewVersion) {

+      //Set process Definition key

+      this.ptd.processDefinitionKey = this.bpmnId.value;

+

+      //Check the process Definition key to get its test definition loaded in.

+      

+      this.checkProcessDefinitionKey();

+    }

+

+    //Listen for any changes made to the diagram and properties panel

+    this.modeler.getModel().on('element.changed', (event) => {

+      //check to see if process definition key has changed

+      if (event.element.type == 'bpmn:Process' && (this.ptd.processDefinitionKey != event.element.id)) {

+        this.ptd.processDefinitionKey = event.element.id;

+        this.checkProcessDefinitionKey();

+      }

+

+      // If it has been deployed, they cannot edit and save it

+      if (!this.ptd.currentInstance.isDeployed) {

+        this.ptd.currentInstance.bpmnHasChanged = true;

+        this.markFormAs('dirty');

+      }

+    });

+

+    this.setInProgress(false);

+

+  }

+

+  //Open a .bpmn file

+  async open() {

+

+    this.setInProgress(true);

+    this.ptd.reset();

+    this.ptd.switchVersion();

+

+    this.fetchFileContents('file', val => {

+      this.setBpmn(false, val);

+    });

+

+  }

+

+  //Get the xml of the default bpmn file

+  async getDefaultFlow() {

+    return new Promise((resolve, reject) => {

+      this._fileTransfer.get('5d0a5357e6624a3ef0d16164').subscribe(

+        data => {

+          let bpmn = new Buffer(data as Buffer);

+          resolve(bpmn.toString());

+        },

+        err => {

+          this.errorPopup(err.toString());

+          reject(err);

+        }

+      );

+    });

+  }

+

+  //set the Modeler

+  async setModeler(options) {

+    if (!this.modeler) {

+      this.modeler = await this._bpmnFactory.setup(options);

+    }

+  }

+

+  async getVersionBpmn() {

+    return new Promise((resolve, reject) => {

+      this._fileTransfer.get(this.ptd.currentInstance.bpmnFileId).subscribe(

+        result => {

+          let bpmn = new Buffer(result as Buffer);

+          resolve(bpmn.toString());

+        },

+        err => {

+          this.errorPopup(err.toString());

+          reject(err);

+        }

+      );

+    });

+  }

+

+  fetchFileContents(elementId, callback) {

+    var val = "x";

+    var fileToLoad = (document.getElementById(elementId))['files'][0];

+    var fileReader = new FileReader();

+    if (!fileToLoad) {

+      return null;

+    }

+

+    fileReader.onload = function (event) {

+      val = event.target['result'] as string;

+      callback(val);

+    }

+    fileReader.readAsText(fileToLoad);

+  }

+

+  /*****************************************

+   * Page Funtionality Methods

+   ****************************************/

+

+  toggleSidebar(set: Boolean) {

+    if (!set) {

+      this.showSidebar = false;

+      this.modelerElement.nativeElement.style.right = '0px';

+    } else {

+      this.showSidebar = true;

+      $(this.modelerElement.nativeElement).css('right', $(this.sidebarElement.nativeElement).width());

+    }

+  }

+

+  toggleProperties() {

+    if (!this.showProperties) {

+      this.toggleSidebar(true);

+      this.showTestDefinition = false;

+      this.showProperties = true;

+    } else {

+      this.toggleSidebar(false);

+      this.showProperties = false;

+    }

+  }

+

+  toggleTestDefinition() {

+    if (!this.showTestDefinition) {

+      this.toggleSidebar(true);

+      this.showProperties = false;

+      this.showTestDefinition = true;

+    } else {

+      this.toggleSidebar(false);

+      this.showTestDefinition = false;

+    }

+

+    this.refresh();

+  }

+

+  refresh() {

+    this.isRefreshed = false;

+    setTimeout(() => {

+      this.isRefreshed = true;

+    }, 1);

+  }

+

+  snackAlert(msg) {

+    this._snack.openFromComponent(AlertSnackbarComponent, {

+      duration: 1500,

+      data: {

+        message: msg

+      }

+    });

+  }

+

+  errorPopup(err) {

+    return this._dialog.open(AlertModalComponent, {

+      width: '400px',

+      data: {

+        type: 'alert',

+        message: err

+      }

+    });

+  }

+}

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
index 0000000..b37984d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/modeler.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { ModelerModule } from './modeler.module';

+

+describe('ModelerModule', () => {

+  let modelerModule: ModelerModule;

+

+  beforeEach(() => {

+    modelerModule = new ModelerModule();

+  });

+

+  it('should create an instance', () => {

+    expect(modelerModule).toBeTruthy();

+  });

+});

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
index 0000000..2dee340
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/modeler.module.ts
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { ModelerRoutingModule } from './modeler-routing.module';

+import { ModelerComponent } from './modeler.component';

+import { MatButtonModule, MatIconModule, MatTableModule, MatFormFieldModule, MatInputModule, MatPaginatorModule, MatBadgeModule, MatCardModule, MatSelectModule, MatOptionModule, MatTabsModule, MatProgressSpinnerModule, MatListModule, MatMenuModule, MatTooltipModule, MatDialogModule } from '@angular/material';

+import { FormsModule } from '@angular/forms';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { TestHeadModalModule } from 'app/shared/modules/test-head-modal/test-head-modal.module';

+import { AlertModalModule } from 'app/shared/modules/alert-modal/alert-modal.module';

+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';

+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

+import { CodemirrorModule } from 'ng2-codemirror';

+import { FileUploadModule } from 'ng2-file-upload';

+import { AlertSnackbarModule } from 'app/shared/modules/alert-snackbar/alert-snackbar.module';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    ModelerRoutingModule,

+    MatButtonModule,

+    MatIconModule,

+    CommonModule,

+    FormsModule,

+    FilterPipeModule,

+    MatButtonModule,

+    MatTableModule,

+    MatFormFieldModule,

+    MatInputModule,

+    MatPaginatorModule,

+    TestHeadModalModule,

+    AlertModalModule,

+    MatBadgeModule,

+    PerfectScrollbarModule,

+    MatCardModule,

+    MatSelectModule,

+    MatOptionModule,

+    MatIconModule,

+    NgbModule,

+    CodemirrorModule,

+    MatTabsModule,

+    MatProgressSpinnerModule,

+    FileUploadModule,

+    MatListModule,

+    MatMenuModule,

+    MatTooltipModule,

+    AlertSnackbarModule

+  ],

+  declarations: [ModelerComponent],

+})

+export class ModelerModule { }

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
index 0000000..73577a0
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/new.bpmn
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<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">

+  <bpmn:process id="new_diagram" name="new_diagram" isExecutable="true">

+    <bpmn:startEvent id="StartEvent_1r2e4pd" camunda:asyncBefore="true">

+      <bpmn:outgoing>SequenceFlow_1gpkkbm</bpmn:outgoing>

+    </bpmn:startEvent>

+    <bpmn:endEvent id="EndEvent_0czvyun">

+      <bpmn:incoming>SequenceFlow_1psgifi</bpmn:incoming>

+      <bpmn:terminateEventDefinition id="TerminateEventDefinition_12nqmmc" />

+    </bpmn:endEvent>

+    <bpmn:task id="Task_0e68ysc" name="VTH:PING TEST">

+      <bpmn:incoming>SequenceFlow_1d4t09c</bpmn:incoming>

+      <bpmn:outgoing>SequenceFlow_14b2mg6</bpmn:outgoing>

+    </bpmn:task>

+    <bpmn:scriptTask id="ScriptTask_1fwzn2i" name="JS Script inline" scriptFormat="JavaScript" camunda:resource="deployment://script.js">

+      <bpmn:incoming>SequenceFlow_14b2mg6</bpmn:incoming>

+      <bpmn:outgoing>SequenceFlow_0i9av57</bpmn:outgoing>

+    </bpmn:scriptTask>

+    <bpmn:task id="Task_10nhde5" name="UTIL:LogTestResult">

+      <bpmn:incoming>SequenceFlow_0i9av57</bpmn:incoming>

+      <bpmn:outgoing>SequenceFlow_1psgifi</bpmn:outgoing>

+    </bpmn:task>

+    <bpmn:task id="Task_1r783jz" name="VTH:PING TEST">

+      <bpmn:incoming>SequenceFlow_1gpkkbm</bpmn:incoming>

+      <bpmn:outgoing>SequenceFlow_1d4t09c</bpmn:outgoing>

+    </bpmn:task>

+    <bpmn:sequenceFlow id="SequenceFlow_1gpkkbm" sourceRef="StartEvent_1r2e4pd" targetRef="Task_1r783jz" />

+    <bpmn:sequenceFlow id="SequenceFlow_1psgifi" sourceRef="Task_10nhde5" targetRef="EndEvent_0czvyun" />

+    <bpmn:sequenceFlow id="SequenceFlow_1d4t09c" sourceRef="Task_1r783jz" targetRef="Task_0e68ysc" />

+    <bpmn:sequenceFlow id="SequenceFlow_14b2mg6" sourceRef="Task_0e68ysc" targetRef="ScriptTask_1fwzn2i" />

+    <bpmn:sequenceFlow id="SequenceFlow_0i9av57" sourceRef="ScriptTask_1fwzn2i" targetRef="Task_10nhde5" />

+  </bpmn:process>

+  <bpmndi:BPMNDiagram id="BPMNDiagram_1">

+    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="new_diagram">

+      <bpmndi:BPMNShape id="StartEvent_1r2e4pd_di" bpmnElement="StartEvent_1r2e4pd">

+        <dc:Bounds x="354" y="117" width="36" height="36" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="416" y="153" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNShape id="EndEvent_0czvyun_di" bpmnElement="EndEvent_0czvyun">

+        <dc:Bounds x="1189" y="125" width="36" height="36" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="1117" y="165" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNShape id="Task_09vptvw_di" bpmnElement="Task_0e68ysc">

+        <dc:Bounds x="673" y="95" width="100" height="80" />

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNShape id="ScriptTask_1fwzn2i_di" bpmnElement="ScriptTask_1fwzn2i">

+        <dc:Bounds x="845" y="95" width="100" height="80" />

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNShape id="Task_10nhde5_di" bpmnElement="Task_10nhde5">

+        <dc:Bounds x="1010" y="95" width="100" height="80" />

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNShape id="Task_0myfoou_di" bpmnElement="Task_1r783jz">

+        <dc:Bounds x="493" y="95" width="100" height="80" />

+      </bpmndi:BPMNShape>

+      <bpmndi:BPMNEdge id="SequenceFlow_1gpkkbm_di" bpmnElement="SequenceFlow_1gpkkbm">

+        <di:waypoint x="390" y="135" />

+        <di:waypoint x="493" y="135" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="441.5" y="114" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNEdge>

+      <bpmndi:BPMNEdge id="SequenceFlow_1psgifi_di" bpmnElement="SequenceFlow_1psgifi">

+        <di:waypoint x="1110" y="135" />

+        <di:waypoint x="1150" y="135" />

+        <di:waypoint x="1150" y="143" />

+        <di:waypoint x="1189" y="143" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="1165" y="133" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNEdge>

+      <bpmndi:BPMNEdge id="SequenceFlow_1d4t09c_di" bpmnElement="SequenceFlow_1d4t09c">

+        <di:waypoint x="593" y="136" />

+        <di:waypoint x="673" y="135" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="633" y="114.5" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNEdge>

+      <bpmndi:BPMNEdge id="SequenceFlow_14b2mg6_di" bpmnElement="SequenceFlow_14b2mg6">

+        <di:waypoint x="773" y="135" />

+        <di:waypoint x="845" y="135" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="809" y="114" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNEdge>

+      <bpmndi:BPMNEdge id="SequenceFlow_0i9av57_di" bpmnElement="SequenceFlow_0i9av57">

+        <di:waypoint x="945" y="135" />

+        <di:waypoint x="1010" y="135" />

+        <bpmndi:BPMNLabel>

+          <dc:Bounds x="977.5" y="114" width="0" height="12" />

+        </bpmndi:BPMNLabel>

+      </bpmndi:BPMNEdge>

+    </bpmndi:BPMNPlane>

+  </bpmndi:BPMNDiagram>

+</bpmn:definitions>

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
index 0000000..5bdd240
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/templates/elements.json
@@ -0,0 +1,20 @@
+{

+  "name": "Virtual Test Head",

+  "id": "com.camunda.example.CallTestHeadTask",

+  "appliesTo": [

+    "bpmn:Task",

+    "bpmn:CallActivity"

+  ],

+  "properties": [

+    {

+      "label": "Name",

+      "type": "String",

+      "value": "VTH:",

+      "editable": true,

+      "binding": {

+        "type": "camunda:field",

+        "name": "camunda:name"

+      }

+    }

+  ]

+}

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
index 0000000..06f62da
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/modeler/test-definition-element.class.ts
@@ -0,0 +1,203 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestDefinition, BpmnInstance, TestHeadRef, Pflow } from "app/shared/models/test-definition.model";

+import { toInteger } from "@ng-bootstrap/ng-bootstrap/util/util";

+

+export class TestDefinitionElement implements TestDefinition  {

+

+    testName: String;

+    testDescription: String;

+    processDefinitionKey: String;

+    groupId: String;

+    bpmnInstances: BpmnInstanceElement[];

+    disabled: Boolean;

+    _id: String;

+    createdAt: String;

+    createdBy: String;

+    updatedAt: String;

+    updatedBy: String;

+

+    currentVersion; // int Array index of the bpmnInstances

+    currentVersionName;

+    currentInstance: BpmnInstanceElement;

+

+    constructor(testDefinition?){

+        if(testDefinition){

+            this.setAll(testDefinition);

+        }else{

+            this.reset();

+        }

+    }

+

+

+    reset(){

+        this._id = "";

+        this.testName = '';

+        this.testDescription = '';

+        this.groupId = '';

+        this.processDefinitionKey = '';

+        this.disabled = false;

+        this.createdAt = null;

+        this.createdBy = null;

+        this.updatedAt = null;

+        this.updatedBy = null;

+

+        this.bpmnInstances = [];

+        this.addBpmnInstance();

+

+        this.switchVersion();

+    }

+

+    switchVersion(version?){

+        if(version){

+            //find the version

+            this.bpmnInstances.forEach((elem, val) => {

+                if(elem['version'] == version){

+                    this.currentVersion = val;

+                    this.currentInstance = this.bpmnInstances[val];

+                    this.currentVersionName = this.currentInstance.version;

+                }

+            });

+        }else{

+            //get latest version

+            this.currentVersion = this.bpmnInstances.length - 1;

+            this.currentInstance = this.bpmnInstances[this.currentVersion];

+            this.currentVersionName = this.currentInstance.version;

+        }

+    }

+

+    //Setter Methods

+

+    setAll(td){

+        this._id = td._id;

+        this.testName = td.testName;

+        this.testDescription = td.testDescription;

+        this.groupId = td.groupId;

+        this.processDefinitionKey = td.processDefinitionKey;

+        this.setBpmnInstances(td.bpmnInstances);

+        this.switchVersion();

+    }

+

+    setId(id: String){

+        this._id = id;

+    }

+

+    setName(testName: String){

+        this.testName = testName;

+    }

+

+    setDescription(testDescription: String){

+        this.testDescription = testDescription;

+    }

+

+    setGroupId(groupId: String){

+        this.groupId = groupId;

+    }

+

+    setProcessDefinitionKey(processDefinitionKey: String){

+        this.processDefinitionKey = processDefinitionKey;

+    }

+

+    setBpmnInstances(instances: BpmnInstanceElement[] = []){

+        

+        

+        this.bpmnInstances = instances;

+    }

+

+    setNewVersion(newVersion: number = null){

+        if(newVersion == null){

+            newVersion = this.bpmnInstances.length;

+        }

+        if(this.setVersion(newVersion) == -1){

+            this.setNewVersion(++newVersion);

+        }

+        return newVersion;

+    }

+

+    setVersion(version){

+        this.bpmnInstances.forEach((elem, val) => {

+            if(elem.version == version && this.currentVersion != val ){

+                return -1;

+            }

+        });

+        this.currentInstance.version = version;

+        return version;

+    }

+

+    addBpmnInstance(instance?){

+        if(!instance){

+           instance = this.newInstance();

+        }

+        //console.log(this.bpmnInstances[this.bpmnInstances.length - 1].version )

+        if(this.bpmnInstances[this.bpmnInstances.length - 1]){

+            instance['version'] = (toInteger(this.bpmnInstances[this.bpmnInstances.length - 1].version) + 1).toString();

+        }else{

+            instance['version'] = "1";      

+        }

+        this.bpmnInstances.push(instance);

+                

+    }

+

+    removeBpmnInstance(version){

+        this.bpmnInstances.forEach((elem, val) =>{

+            if(elem['version'] == version){

+                this.bpmnInstances.splice(val, 1);

+            }

+        });

+    }

+

+    getBpmnInstances(version: String = null){

+        if(!version)

+            return this.bpmnInstances;

+        

+        this.bpmnInstances.forEach((elem, val) => {

+            if(elem['version'] == version){

+                return elem;

+            }

+        });

+    }

+

+    newInstance(): BpmnInstanceElement {

+        return {} as BpmnInstanceElement;

+    }

+

+

+

+

+

+}

+

+export class BpmnInstanceElement implements BpmnInstance {

+    createdAt: String;

+    updatedAt: String;

+    processDefinitionId: String;    

+    deploymentId: String;

+    version: String;

+    bpmnFileId: String;

+    resourceFileId: String;

+    isDeployed: Boolean;

+    testHeads: TestHeadRef[];

+    pflos: Pflow[];

+    testDataTemplate: String;

+    testDataTemplateJSON: any;

+    updatedBy: String;

+    createdBy: String;

+

+    bpmnXml: String;

+    bpmnHasChanged: Boolean;

+

+}
\ 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
index 0000000..cedbf19
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test-routing.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { CreateTestComponent } from './create-test.component';

+

+const routes: Routes = [

+  {

+    path:'', component: CreateTestComponent

+  }

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class CreateTestRoutingModule { }

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
index 0000000..38be727
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.pug
@@ -0,0 +1,92 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div 

+  h2 Onboarding

+  ol.breadcrumb.bg-light

+    li.breadcrumb-item

+      a(href="#") Onboarding

+    li.breadcrumb-item

+      a(href="#") Test Heads

+    li.breadcrumb-item.active Test Definition

+

+  .row([@routerTransition])

+    .col-12

+      mat-card.mb-5

+        mat-card-header.bg-primary.text-white

+          mat-card-title

+            h4 Create Test Definition

+        mat-card-content

+          app-create-test-form([listKey]="listKey")

+      //.card.border-primary

+        .card-header.bg-primary.text-white.text-center

+          h4 Create New Tests

+        .card-body

+          app-create-test-form

+

+    //.col-sm-4

+      h4 Saved Tests

+

+      input.form-control.bg-light.mb-3([(ngModel)]="search.testName", type="text", placeholder="Search...")

+      .list-group

+          a.list-group-item.list-group-item-action(*ngFor="let test of test_list | filterBy: search", style="cursor:pointer")

+            b {{ test.testName }}

+

+

+//div

+  

+

+  .row

+    .col-sm-10

+      app-page-header([heading]="'Onboarding'", [icon]="'fa-edit'")

+      ol.breadcrumb.bg-light

+        li.breadcrumb-item

+          a(href="#") Onboarding

+        li.breadcrumb-item

+          a(href="#") Test Heads

+        li.breadcrumb-item

+          a(href="#") Test Strategies

+        li.breadcrumb-item.active Create Test

+      

+    //  div

+        div.ps(style="position: relative; max-width: 600px; max-height: 40px;", [perfectScrollbar]="") 

+          div hi

+

+      .footer.row.p-2.bg-light([@routerTransition], style="box-shadow:inset 0px 11px 8px -10px #CCC, inset 0px -11px 8px -10px #CCC; bottom:44px", )

+        .col-sm-12

+          div(style="width:100%")

+            div(style="dispaly:inline-block") Test Strategies

+            div(style="display:inline-block") hi

+            //input.form-control.col-sm-2(type="text", style="dispaly:inline-block")

+        .ps(style="position: relative; max-height: 110px", [perfectScrollbar]="config")

+          div.col-sm-12.mb-3(style="white-space: nowrap;")

+            .card.mr-3(*ngFor="let vts of vts_list", style="display:inline-block;border-color: #045C87")

+              .card-header.text-white(style="background:#045C87") {{ vts.test_strategy_name ? vts.test_strategy_name : vts.test_strategy_id }}

+              .card-body

+                img(src="assets/images/VNFHealth.PNG", style="width:100px")

+  

+    //.sidebar-right.col-sm-2.bg-light.p-2(style="box-shadow:inset 11px 0px 8px -10px #CCC, inset -11px 0px 8px -10px #CCC;")

+      .ps(style="position: relative;", [perfectScrollbar]="")

+        .card.mb-3.border-primary(*ngFor="let vth of vth_list")

+          .card-header.text-white.bg-primary {{ vth.test_head_name ? vth.test_head_name : vth.test_head_id }}

+          .card-body

+            | {{ vth.description }}

+

+.footer.bg-primary

+  .row.p-2

+    .col-sm-12

+     // button(mat-raised-button, color="primary", (click)="back()") Back

+      button.pull-right(mat-raised-button, color="accent", (click)="next()") Next

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
index 0000000..7d236e4
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.scss
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.sidebar-right {

+    margin-top: -15px;

+}
\ 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
index 0000000..ff339f5
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { CreateTestComponent } from './create-test.component';

+

+describe('CreateTestComponent', () => {

+  let component: CreateTestComponent;

+  let fixture: ComponentFixture<CreateTestComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ CreateTestComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(CreateTestComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..abb9ee6
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.component.ts
@@ -0,0 +1,73 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Output } from '@angular/core';

+import { routerLeftTransition } from '../../../router.animations';

+import { ListService } from '../../../shared/services/list.service';

+import { HttpClient } from '@angular/common/http';

+import { AppGlobals } from '../../../app.global';

+import { Router,  NavigationExtras } from '@angular/router';

+import { TestDefinitionService } from '../../../shared/services/test-definition.service';

+

+

+@Component({

+  selector: 'app-create-test',

+  templateUrl: './create-test.component.pug',

+  styleUrls: ['./create-test.component.scss', '../onboarding.component.scss'],

+  providers: [AppGlobals],

+  animations: [routerLeftTransition()]

+})

+export class CreateTestComponent implements OnInit {

+

+  public test_list = [];

+  public search;

+

+  @Output() public listKey;

+

+  constructor(private router: Router, private testDefinition: TestDefinitionService, private list: ListService, private http: HttpClient, private _global: AppGlobals) {

+

+  }

+

+  back() {

+    this.router.navigateByUrl('/onboarding/test-head');

+  }

+

+  next() {

+    let navigationExtras: NavigationExtras = {

+      queryParams: {

+          "testDefinition": JSON.stringify(this.test_list[this.test_list.length - 1])

+      }

+    };

+    this.router.navigate(['/onboarding/test-instances'], navigationExtras);

+  }

+

+  ngOnInit() {

+

+    this.search = {};

+    this.search.testName = "";

+

+    this.listKey = 'td';

+

+    //Create List with list service

+    this.list.createList(this.listKey);

+

+    //Subscribe to list service

+    this.list.listMap[this.listKey].currentList.subscribe((list) =>{

+      this.test_list = list;

+    });

+  }

+

+}

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
index 0000000..db72c89
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { CreateTestModule } from './create-test.module';

+

+describe('CreateTestModule', () => {

+  let createTestModule: CreateTestModule;

+

+  beforeEach(() => {

+    createTestModule = new CreateTestModule();

+  });

+

+  it('should create an instance', () => {

+    expect(createTestModule).toBeTruthy();

+  });

+});

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
index 0000000..576948d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/create-test/create-test.module.ts
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { CreateTestRoutingModule } from './create-test-routing.module';

+import { CreateTestComponent } from './create-test.component';

+import { PageHeaderModule } from '../../../shared';

+import { PERFECT_SCROLLBAR_CONFIG, PerfectScrollbarConfigInterface } from 'ngx-perfect-scrollbar';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { FormsModule } from '@angular/forms';

+import { MatCheckboxModule } from '@angular/material/checkbox';

+import { MatRadioModule } from '@angular/material/radio';

+import { MatInputModule } from '@angular/material/input';

+import { MatIconModule } from '@angular/material/icon';

+import { MatButtonModule, MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS, MAT_CHECKBOX_CLICK_ACTION, MatCardModule } from '@angular/material';

+import { CreateTestFormModule } from '../../../shared/modules/create-test-form/create-test-form.module';

+

+const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {

+  suppressScrollY: true

+};

+

+@NgModule({

+  imports: [

+    CommonModule,

+    CreateTestRoutingModule,

+    CreateTestFormModule,

+    PageHeaderModule,

+    FilterPipeModule,

+    FormsModule,

+    MatButtonModule,

+    MatDialogModule,

+    MatCheckboxModule,

+    MatRadioModule,

+    MatInputModule,

+    MatIconModule,

+    CreateTestFormModule,

+    MatCardModule

+  ],

+  declarations: [CreateTestComponent],

+  providers: [

+    {provide: PERFECT_SCROLLBAR_CONFIG, useValue: DEFAULT_PERFECT_SCROLLBAR_CONFIG},

+    {provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}},

+    {provide: MAT_CHECKBOX_CLICK_ACTION, useValue: 'check'}

+  ]

+})

+export class CreateTestModule { }

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
index 0000000..92dabc3
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/onboarding-routing.module.ts
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { OnboardingComponent } from './onboarding.component';

+

+const routes: Routes = [

+  {

+    path: '', component: OnboardingComponent,

+    children:[

+      { path: '', redirectTo: 'start', pathMatch: 'prefix' },

+      { path: 'test-definition', loadChildren: './create-test/create-test.module#CreateTestModule' },

+      { path: 'start', loadChildren: './start/start.module#StartModule' },

+      { path: 'test-head', loadChildren: './test-head/test-head.module#TestHeadModule' },

+      { path: 'test-instances', loadChildren: './test-instances/test-instances.module#TestInstancesModule' }

+    ]

+  }

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class OnboardingRoutingModule { }

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
index 0000000..48d9530
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/onboarding.component.pug
@@ -0,0 +1,83 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+router-outlet

+//div([@routerTransition])

+  app-page-header([heading]="'Onboarding'", [icon]="'fa-edit'")

+  .row

+

+    .col-lg-6

+  

+      .card.mb-12

+        .card-header

+          | Virtual Test Heads

+        .card-body

+          .list-group

+            a.list-group-item.list-group-item-action(href="#",*ngFor="let vth of vth_list")

+              b {{ vth.test_head_id }}

+              p {{ vth.description }}

+              a(href="{{ vth.url_path }}") {{ vth.url_path }}

+

+    .col-lg-6

+

+      .card.bg-light.mb-12

+        .card-header

+          | New Virtual Test Head

+        .card-body

+          form(role='form')

+            fieldset.form-group

+              label Test Head ID

+              input.form-control(type="text", )

+              p

+            

+              label Test Head Name

+              input.form-control(type="text", )

+              p

+

+              label Description

+              input.form-control(type="text",)

+              p

+

+              label Test Head Type

+              select.form-control()

+                option Proxy

+                option Regular

+                option Script

+                option Adapter

+              p

+              

+              label Implementation Language

+              select.form-control()

+                option Java

+                option Python

+                option Javascript/NodeJS 

+              p

+              

+              label Base URL

+              input.form-control(type="url")

+              p

+

+              label Request Method

+              input.form-control(type="url")

+              p

+

+              label Creator

+              input.form-control(type="text")

+              p

+

+              button.btn.btn-primary((click)='next($event)') Submit 

+

+ <router-outlet></router-outlet>

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
index 0000000..4e64050
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/onboarding.component.scss
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.footer {

+    position: fixed;

+    left: 235px;

+    bottom: 0px;

+    right: 0px;

+    z-index: 100;

+}

+

+@media screen and (max-width: 992px) {

+    .footer {

+        left: 0px;

+    }

+}
\ 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
index 0000000..9268aab
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/onboarding.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { OnboardingComponent } from './onboarding.component';

+

+describe('OnboardingComponent', () => {

+  let component: OnboardingComponent;

+  let fixture: ComponentFixture<OnboardingComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ OnboardingComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(OnboardingComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..9c3b288
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/onboarding.component.ts
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { routerLeftTransition } from '../../router.animations';

+

+@Component({

+  selector: 'app-onboarding',

+  templateUrl: './onboarding.component.pug',

+  styleUrls: ['./onboarding.component.scss'],

+  animations: [routerLeftTransition()]

+})

+export class OnboardingComponent implements OnInit {

+

+  constructor() {}

+  ngOnInit() {}

+}

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
index 0000000..9a7068e
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/onboarding.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { OnboardingModule } from './onboarding.module';

+

+describe('OnboardingModule', () => {

+  let onboardingModule: OnboardingModule;

+

+  beforeEach(() => {

+    onboardingModule = new OnboardingModule();

+  });

+

+  it('should create an instance', () => {

+    expect(onboardingModule).toBeTruthy();

+  });

+});

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
index 0000000..3e01611
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/onboarding.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { OnboardingRoutingModule } from './onboarding-routing.module';

+import { OnboardingComponent } from './onboarding.component';

+import { PageHeaderModule } from '../../shared';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    OnboardingRoutingModule,

+    PageHeaderModule

+  ],

+  declarations: [OnboardingComponent]

+})

+export class OnboardingModule { }

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
index 0000000..43661d4
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/onboarding.service.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { Observable } from 'rxjs'; 

+

+@Injectable()

+export class OnboardingService {

+

+    private _url: string = 'http://localhost:3000/api/vth;';

+

+    constructor(private http: HttpClient) {}

+

+    getVirtualTestHeads() {

+        return this.http.get(this._url);

+    }

+

+}
\ 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
index 0000000..93fe1ce
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/start/start-routing.module.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { StartComponent } from './start.component';

+

+const routes: Routes = [{

+  path:'', component: StartComponent

+}];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class StartRoutingModule { }

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
index 0000000..23781db
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/start/start.component.pug
@@ -0,0 +1,24 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+.row.fullWidth(style="margin-top:-15px;background-image:url('assets/images/networkBackground.jpg'); background-size:cover; backgroung-repeat:no-repeat; min-height:970px;")

+  .col-sm-12

+    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")

+      h1.text-center.text-white Onboarding

+

+      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!

+      div.text-center

+        button(mat-raised-button, color="accent", (click)="next()") Get Started

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/start/start.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..b3f164c
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/start/start.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { StartComponent } from './start.component';

+

+describe('StartComponent', () => {

+  let component: StartComponent;

+  let fixture: ComponentFixture<StartComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ StartComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(StartComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..899f8dc
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/start/start.component.ts
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { Router } from '@angular/router';

+

+@Component({

+  selector: 'app-start',

+  templateUrl: './start.component.pug',

+  styleUrls: ['./start.component.scss']

+})

+export class StartComponent implements OnInit {

+

+  constructor(private router: Router) { }

+

+  public next(){

+    this.router.navigateByUrl('/onboarding/test-head');

+  }

+  ngOnInit() {

+  }

+

+}

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
index 0000000..b198e76
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/start/start.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { StartModule } from './start.module';

+

+describe('StartModule', () => {

+  let startModule: StartModule;

+

+  beforeEach(() => {

+    startModule = new StartModule();

+  });

+

+  it('should create an instance', () => {

+    expect(startModule).toBeTruthy();

+  });

+});

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
index 0000000..4a236c1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/start/start.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { StartRoutingModule } from './start-routing.module';

+import { StartComponent } from './start.component';

+import { MatButtonModule } from '@angular/material';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    StartRoutingModule,

+    MatButtonModule

+  ],

+  declarations: [StartComponent]

+})

+export class StartModule { }

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
index 0000000..c166661
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head-routing.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { TestHeadComponent } from './test-head.component';

+

+const routes: Routes = [

+  {

+    path:'', component: TestHeadComponent

+  }

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class TestHeadRoutingModule { }

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
index 0000000..69ef306
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.pug
@@ -0,0 +1,52 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+  app-page-header([heading]="'Onboarding'", [icon]="'fa-edit'")

+  

+  ol.breadcrumb.bg-light

+    li.breadcrumb-item

+      a(href="#") Onboarding

+    li.breadcrumb-item.active Test Heads

+

+

+  .row.mb-5

+    .col-sm-8

+      mat-card

+        mat-card-header.bg-primary.text-white

+          mat-card-title 

+            h4 Create Test Head

+        mat-card-content

+          app-create-test-head-form([options]="createFormOptions")

+      //.card.mb-12.bg-light.border-primary

+        .card-header.bg-primary.text-white.text-center

+          h4 New Virtual Test Head

+        .card-body

+          app-create-test-head-form([options]="createFormOptions")

+

+    .col-sm-4

+      h4 Saved Test Heads

+

+      input.form-control.bg-light.mb-3([(ngModel)]="search.testHeadName", type="text", placeholder="Search...")

+      .list-group

+          a.list-group-item.list-group-item-action((click)="openTestHead(vth)",*ngFor="let vth of vth_list | filterBy: search", style="cursor:pointer")

+            b {{ vth.testHeadName }}

+

+.footer.bg-primary

+  .row.p-2

+    .col-sm-12

+      //button(mat-raised-button, color="primary", (click)="back()") Back

+      //button.pull-right(mat-raised-button, color="accent", (click)="next()") Next

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..766b121
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestHeadComponent } from './test-head.component';

+

+describe('TestHeadComponent', () => {

+  let component: TestHeadComponent;

+  let fixture: ComponentFixture<TestHeadComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestHeadComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestHeadComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..136932a
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.component.ts
@@ -0,0 +1,91 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewContainerRef, Output, Input } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { routerLeftTransition } from '../../../router.animations';

+import { Router } from '@angular/router';

+import { ListService } from '../../../shared/services/list.service';

+import { TestHeadModalComponent } from '../../../shared/modules/test-head-modal/test-head-modal.component';

+import { MatDialog } from '@angular/material';

+import { TestHeadService } from '../../../shared/services/test-head.service';

+

+@Component({

+  selector: 'app-test-head',

+  templateUrl: './test-head.component.pug',

+  styleUrls: ['./test-head.component.scss', '../onboarding.component.scss'],

+  animations: [routerLeftTransition()]

+})

+export class TestHeadComponent implements OnInit {

+

+  public vth_list;

+  public search;

+

+  @Output() public createFormOptions = {

+    goal: 'create'  

+  }

+

+  constructor(

+    private router: Router, 

+    private list: ListService,

+    public dialog: MatDialog,

+    private testHead: TestHeadService

+  ) {

+    

+   }

+

+  next() {

+    this.router.navigateByUrl('/onboarding/test-definition');

+  }

+

+  back() {

+    this.router.navigateByUrl('/onboarding');

+  }

+

+  openTestHead(testHead): void {

+    const dialogRef = this.dialog.open(TestHeadModalComponent, {

+      width: '450px',

+      data: {

+        goal: 'edit',

+        testHead: testHead

+      }

+    });

+

+    dialogRef.afterClosed().subscribe(result => {

+      

+    });

+  }

+

+  ngOnInit() {

+

+    this.search = {};

+    this.search._id = "";

+    this.search.testHeadName = "";

+

+    this.list.createList('vth');

+    

+    this.testHead.find({$limit: -1})

+      .subscribe((vth_list) => {

+        this.list.changeMessage('vth', vth_list);

+      });

+

+    this.list.listMap['vth'].currentList.subscribe((list) =>{

+      this.vth_list = list;

+    });

+

+  }

+

+}

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
index 0000000..a631784
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestHeadModule } from './test-head.module';

+

+describe('TestHeadModule', () => {

+  let testHeadModule: TestHeadModule;

+

+  beforeEach(() => {

+    testHeadModule = new TestHeadModule();

+  });

+

+  it('should create an instance', () => {

+    expect(testHeadModule).toBeTruthy();

+  });

+});

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
index 0000000..96a3f40
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-head/test-head.module.ts
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule, Pipe } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { TestHeadRoutingModule } from './test-head-routing.module';

+import { TestHeadComponent } from './test-head.component';

+import { PageHeaderModule, SharedPipesModule } from '../../../shared';

+import { FormsModule } from '@angular/forms';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { ListService } from '../../../shared/services/list.service';

+import { CreateTestHeadFormModule } from '../../../shared/modules/create-test-head-form/create-test-head-form.module';

+import { CreateTestHeadFormComponent } from '../../../shared/modules/create-test-head-form/create-test-head-form.component';

+import { TestHeadModalModule } from '../../../shared/modules/test-head-modal/test-head-modal.module';

+import { MatButtonModule, MatCardModule } from '@angular/material';

+

+//import { CreateTestHeadFormComponent } from '../../../shared/modules/create-test-head-form/create-test-head-form.component';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    TestHeadRoutingModule,

+    CreateTestHeadFormModule,

+    PageHeaderModule,

+    FormsModule,

+    FilterPipeModule,

+    TestHeadModalModule,

+    MatButtonModule,

+    MatCardModule

+  ],

+  declarations: [

+    TestHeadComponent

+  ]

+

+})

+

+export class TestHeadModule { }

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
index 0000000..93d3967
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances-routing.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { TestInstancesComponent } from './test-instances.component';

+

+const routes: Routes = [

+  {

+    path:'', component: TestInstancesComponent

+  }

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class TestInstancesRoutingModule { }

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
index 0000000..dcf7bc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.pug
@@ -0,0 +1,35 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div

+  app-page-header([heading]="'Onboarding'", [icon]="'fa-edit'")

+  script(src="../../../../../node_modules/codemirror/mode/yaml/yaml.js")

+  //- ol.breadcrumb.bg-light

+  //-   li.breadcrumb-item

+  //-     a(href="#") Onboarding

+  //-   li.breadcrumb-item

+  //-     a(href="#") Test Heads

+  //-   li.breadcrumb-item

+  //-     a(href="#") Test Definition

+  //-   li.breadcrumb-item.active Test Instances

+

+  .col-12([@routerTransition])

+    app-create-test-instance-form([editInstance] = null)

+.footer.bg-primary

+  .row.p-2

+    .col-sm-12

+      //- button(mat-raised-button, color="primary", (click)="back()") Back

+      //- button.pull-right(mat-raised-button, color="accent", (click)="next()") Finish

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..481a5ce
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestInstancesComponent } from './test-instances.component';

+

+describe('TestInstancesComponent', () => {

+  let component: TestInstancesComponent;

+  let fixture: ComponentFixture<TestInstancesComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestInstancesComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestInstancesComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..0ab8c5a
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.component.ts
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Output } from '@angular/core';

+import { Router, ActivatedRoute } from '@angular/router';

+import { routerLeftTransition } from '../../../router.animations';

+import 'codemirror/mode/yaml/yaml.js';

+

+@Component({

+  selector: 'app-test-instances',

+  templateUrl: './test-instances.component.pug',

+  styleUrls: ['./test-instances.component.scss', '../onboarding.component.scss'],

+  animations: [routerLeftTransition()]

+})

+export class TestInstancesComponent implements OnInit {

+  yaml;

+

+  public codeConfig = {

+    mode: "yaml",

+    theme: "eclipse",

+    lineNumbers: true

+  };

+

+  

+  //@Output() public createFormOptions;

+  

+

+  constructor(private router: Router, private route: ActivatedRoute) { }

+

+  ngOnInit() {

+    // this.route.queryParams.subscribe(params => {

+    //   this.createFormOptions = params["testDefinition"];

+    // });

+    

+  }

+

+  // back() {

+  //   this.router.navigateByUrl('/onboarding/test-definition');

+  // }

+

+  // next() {

+  //   this.router.navigateByUrl('/dashboard');

+  // }

+}
\ 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
index 0000000..f20101d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestInstancesModule } from './test-instances.module';

+

+describe('TestInstancesModule', () => {

+  let testInstancesModule: TestInstancesModule;

+

+  beforeEach(() => {

+    testInstancesModule = new TestInstancesModule();

+  });

+

+  it('should create an instance', () => {

+    expect(testInstancesModule).toBeTruthy();

+  });

+});

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
index 0000000..c0cd6d1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/onboarding/test-instances/test-instances.module.ts
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { TestInstancesRoutingModule } from './test-instances-routing.module';

+import { TestInstancesComponent } from './test-instances.component';

+import { PageHeaderModule } from '../../../shared';

+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { FormsModule } from '@angular/forms';

+import { MatButtonModule, MatDialogModule, MatCheckboxModule, MatRadioModule, MatInputModule, MatIconModule, MatExpansionModule, MatCardModule } from '@angular/material';

+import { CreateTestFormModule } from '../../../shared/modules/create-test-form/create-test-form.module';

+import { CodemirrorModule } from 'ng2-codemirror';

+import { CreateTestInstanceFormModule } from '../../../shared/modules/create-test-instance-form/create-test-instance-form.module';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    PageHeaderModule,

+    TestInstancesRoutingModule,

+    FilterPipeModule,

+    FormsModule,

+    MatButtonModule,

+    MatCheckboxModule,

+    MatRadioModule,

+    MatInputModule,

+    MatIconModule,

+    MatExpansionModule,

+    CodemirrorModule,

+    MatCardModule,

+    CreateTestInstanceFormModule

+  ],

+  declarations: [TestInstancesComponent]

+})

+export class TestInstancesModule { }

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
index 0000000..9ea8f4b
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/robot-report/robot-report.component.pug
@@ -0,0 +1,24 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+mat-tab-group((selectedTabChange)="refresh()", dinamicHeight, color="", backgroundColor="")

+    mat-tab(label="Report", *ngIf="reports && reports.report", active='true')

+        iframe(#frame1, width="100%", height='500px', id="frame1", name="frame1", [srcdoc]="reports.report")

+    mat-tab(label="Log", *ngIf="reports && reports.log")

+        iframe(#frame2, width="100%", height='500px', id="frame2", name="frame2", [srcdoc]="reports.log")

+    mat-tab(label="Output", *ngIf="reports && reports.output")

+        codemirror(*ngIf="isRefreshed", [config]="codeConfig", [value]="reports.output")

+        
\ 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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/robot-report/robot-report.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..8999f4d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/robot-report/robot-report.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { RobotReportComponent } from './robot-report.component';

+

+describe('RobotReportComponent', () => {

+  let component: RobotReportComponent;

+  let fixture: ComponentFixture<RobotReportComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ RobotReportComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(RobotReportComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..a790fd6
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/robot-report/robot-report.component.ts
@@ -0,0 +1,82 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, ViewChild, ElementRef, Renderer2, NgModule, Compiler, ViewContainerRef, Inject, Sanitizer } from '@angular/core';

+import { FileTransferService } from 'app/shared/services/file-transfer.service';

+import { DomSanitizer } from '@angular/platform-browser'

+import * as $ from 'jquery';

+import 'codemirror/mode/xml/xml.js';

+

+

+

+@Component({

+  selector: 'app-robot-report',

+  templateUrl: './robot-report.component.pug',

+  styleUrls: ['./robot-report.component.scss'],

+})

+export class RobotReportComponent implements OnInit {

+

+  @Input() public response;

+

+  @ViewChild('srcipts') scripts: ElementRef;

+  @ViewChild('frame1') frame1: ElementRef;

+  @ViewChild('frame2') frame2: ElementRef;

+  @ViewChild('codeMirror') codeMirror: ElementRef;

+

+  @ViewChild('container', {read: ViewContainerRef}) public container;

+

+  public reports = {

+    log: null,

+    report: null,

+    output: null

+  };

+

+  public codeConfig = {

+    mode: "application/xml",

+    theme: "eclipse",

+    readonly: true,

+    lineNumbers: true

+  };

+

+  public isRefreshed = false;

+  public noClick = "<script>$(document).ready(function(){ $('div a').removeAttr('href');}); $(document).click(function(){$('div a').removeAttr('href');} )</script>";

+

+  constructor(private fileTransfer: FileTransferService, private compiler: Compiler, private sanitizer: DomSanitizer) { }

+

+  ngOnInit() {

+    if(this.response){

+      if(this.response.vthResponse && this.response.vthResponse.resultData){

+        let fileId = this.response.vthResponse.resultData.robotResultFileId;

+        if(fileId){

+          this.fileTransfer.get(fileId, {robot: true}).subscribe(result => {

+            this.reports.log = this.sanitizer.bypassSecurityTrustHtml(result['log.html'] + this.noClick);

+            this.reports.report = this.sanitizer.bypassSecurityTrustHtml(result['report.html'] + this.noClick);

+            this.reports.output = result['output.xml'];

+          });

+        }

+      }

+    }

+

+  }

+

+  refresh(){

+    this.isRefreshed = false;

+    setTimeout(() => {

+      this.isRefreshed = true;

+    }, 500);

+  }

+

+}

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
index 0000000..7d299e3
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/scheduling/scheduling-routing.module.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { SchedulingComponent } from './scheduling.component';

+

+const routes: Routes = [{

+  path:'', component: SchedulingComponent}];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class SchedulingRoutingModule { }

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
index 0000000..c70c807
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/scheduling/scheduling.component.pug
@@ -0,0 +1,52 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+  app-page-header([heading]="'Scheduling'", [icon]="'fa-edit'")

+  .card-mb-12

+    .card-body

+      .row

+        div.col-6

+          input.form-control.bg-light.mb-1( type="text", placeholder="Search...")

+        div.col-6

+          button.pull-right.mb-1(mat-raised-button, color="primary", (click)='createSchedule()') Schedule a Test

+

+      table.mat-elevation-z8.text-center(mat-table, [dataSource]="dataSource", style="width: 100%")

+

+        ng-container(matColumnDef="name")

+          th(mat-header-cell, *matHeaderCellDef) Instance Name

+          td(mat-cell, *matCellDef="let element") {{ element.testInstanceName}}

+

+        ng-container(matColumnDef="description")

+          th(mat-header-cell, *matHeaderCellDef)  Date Last Run

+          td(mat-cell, *matCellDef="let element") {{ element.lastRunAt }}

+

+        ng-container(matColumnDef="testDefinition")

+          th(mat-header-cell, *matHeaderCellDef) Date Next Run

+          td(mat-cell, *matCellDef="let element") {{ element.nextRunAt }}

+

+        ng-container(matColumnDef="options")

+          th(mat-header-cell, *matHeaderCellDef) Options

+          td(mat-cell, *matCellDef="let element")

+            button.mr-3(mat-mini-fab, aria-label='View', color="primary", (click)='viewSchedule(element)')

+              i.fa.fa-eye

+            button.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='deleteSchedule(element)')

+              i.fa.fa-remove

+

+        tr(mat-header-row, *matHeaderRowDef="displayedColumns")

+        tr(mat-row, *matRowDef="let row; columns: displayedColumns")

+

+    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
index 0000000..b951c73
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/scheduling/scheduling.component.scss
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.mbtn:focus {

+	outline: none;

+}

+.mat-warn {

+    background-color: red;

+    color:red;

+}

+.bg-accent{

+    background-color: brown

+}

+.mat-mini-fab{

+    width: 30px !important;

+    height: 30px !important;

+    line-height: 10px !important;

+}

+

+

+tr:nth-child(even){background-color: #f2f2f2;}

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
index 0000000..26ecee1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/scheduling/scheduling.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { SchedulingComponent } from './scheduling.component';

+

+describe('SchedulingComponent', () => {

+  let component: SchedulingComponent;

+  let fixture: ComponentFixture<SchedulingComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ SchedulingComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(SchedulingComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..16e9d6f
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/scheduling/scheduling.component.ts
@@ -0,0 +1,154 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewContainerRef, ViewChild } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { routerLeftTransition } from '../../router.animations';

+import { ListService } from '../../shared/services/list.service';

+import { Router } from '@angular/router';

+import { MatDialog, MatTableDataSource, MatPaginator, MatSnackBar } from '@angular/material';

+import { ScheduleTestModalComponent } from '../../shared/modules/schedule-test-modal/schedule-test-modal.component';

+import { SchedulingService } from '../../shared/services/scheduling.service';

+import { TestInstanceService } from '../../shared/services/test-instance.service';

+import { AlertModalComponent } from '../../shared/modules/alert-modal/alert-modal.component';

+import { ViewScheduleModalComponent } from '../../shared/modules/view-schedule-modal/view-schedule-modal.component';

+import { AlertSnackbarComponent } from '../../shared/modules/alert-snackbar/alert-snackbar.component';

+

+@Component({

+  selector: 'app-scheduling',

+  templateUrl: './scheduling.component.pug',

+  styleUrls: ['./scheduling.component.scss'],

+  animations: [routerLeftTransition()]

+})

+

+export class SchedulingComponent implements OnInit {

+

+  constructor(private http: HttpClient,

+    private router: Router,

+    private viewRef: ViewContainerRef,

+    private list: ListService,

+    private schedulingService: SchedulingService,

+    private testInstanceService: TestInstanceService,

+    public dialog: MatDialog,

+    private snack: MatSnackBar

+    ) { }

+

+  public search;

+  public data;

+  public dataSource;

+  public displayedColumns: string[] = ['name', 'description', 'testDefinition', 'options'];

+  public resultsLength;

+  public instances;

+  public temp;

+  public count;

+  

+  @ViewChild(MatPaginator) paginator: MatPaginator;

+

+  ngOnInit() {

+    this.search = {};

+    this.search._id = "";

+    this.search.testInstanceName = "";

+    this.instances = [];

+    this.list.createList('schedules');

+    this.temp = {};

+    this.count = 0;

+

+    this.schedulingService.find({$limit: -1, 'data.testSchedule._testInstanceStartDate': { $ne: ['now'] }}).subscribe((list) => {

+      

+        for(var i = 0; i < Object.keys(list).length; i++){

+          list[i].nextRunAt = this.convertDate(list[i].nextRunAt);

+          list[i].lastRunAt = this.convertDate(list[i].lastRunAt);

+        }

+        this.list.changeMessage('schedules', list);

+    })

+

+    

+    this.dataSource = new MatTableDataSource();

+    this.dataSource.paginator = this.paginator;

+    

+    this.list.listMap['schedules'].currentList.subscribe((list) =>{

+      if(list){

+        this.dataSource.data = list;

+        this.resultsLength = list.length;

+        

+       

+      }

+    });

+  }

+

+  convertDate(str){

+    if(!str){

+      return 'none'

+    }

+    str = str.split('-')

+    let dayAndTime = str[2];

+    let day = dayAndTime.substring(0, dayAndTime.indexOf('T'));

+    let time = dayAndTime.substring(dayAndTime.indexOf('T')+1, dayAndTime.length-5);

+    return  str[1] + '/' + day + '/' + str[0] + ' at ' + time + ' UTC';

+  }

+

+  viewSchedule(sched){

+    this.dialog.open(ViewScheduleModalComponent, {

+      width: '450px',

+      data: sched

+    });

+

+  }

+

+  deleteSchedule(sched){

+    const dialogRef = this.dialog.open(AlertModalComponent, {

+      width : '450px',

+      data: {

+        type: 'confirmation',

+        message: 'Are you sure you want to delete you schedule? This action cannot be undone.'

+      }

+    });

+    dialogRef.afterClosed().subscribe(result => {

+      if(result){

+        this.schedulingService.delete(sched._id).subscribe((result) => {

+          this.snack.openFromComponent(AlertSnackbarComponent, {

+            duration: 1500,

+            data: { 

+              message:'Test Instance Saved'

+            }

+          });

+          this.list.removeElement('sched', '_id', sched._id + '');

+          this.list.listMap['sched'].currentList.subscribe(x => {

+              this.dataSource = x;

+          });

+

+        })

+      }

+    })

+    

+  }

+

+  createSchedule(){

+    const dialogRef = this.dialog.open(ScheduleTestModalComponent, {

+      width: '90%'

+    });

+

+    dialogRef.afterClosed().subscribe(result => {

+      /*if(result != ''){

+        this.test_instance_selected = result;

+        this.strategy_selected = true;

+      }else{

+        this.strategy_selected = false;

+      }*/

+    });

+  }

+

+}

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
index 0000000..b75cb0e
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/scheduling/scheduling.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { SchedulingModule } from './scheduling.module';

+

+describe('SchedulingModule', () => {

+  let schedulingModule: SchedulingModule;

+

+  beforeEach(() => {

+    schedulingModule = new SchedulingModule();

+  });

+

+  it('should create an instance', () => {

+    expect(schedulingModule).toBeTruthy();

+  });

+});

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
index 0000000..00f7e12
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/scheduling/scheduling.module.ts
@@ -0,0 +1,52 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { SchedulingRoutingModule } from './scheduling-routing.module'

+import { SchedulingComponent } from './scheduling.component';

+import { PageHeaderModule } from '../../shared/modules';

+import { MatButtonModule, MatIconModule, MatDatepickerModule, MatCheckboxModule, MatTableModule, MatFormFieldModule, MatInputModule, MatPaginatorModule, MatSnackBarModule} from '@angular/material';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { ScheduleTestModalModule } from '../../shared/modules/schedule-test-modal/schedule-test-modal.module';

+import { AlertModalModule } from '../../shared/modules/alert-modal/alert-modal.module';

+import { ViewScheduleModalModule } from '../../shared/modules/view-schedule-modal/view-schedule-modal.module';

+

+

+@NgModule({

+  imports: [

+    CommonModule,

+    SchedulingRoutingModule,

+    ViewScheduleModalModule,

+    AlertModalModule,

+    MatTableModule, 

+    MatFormFieldModule, 

+    MatInputModule, 

+    MatPaginatorModule,

+    FilterPipeModule,

+    MatButtonModule,

+    MatCheckboxModule,

+    MatDatepickerModule,

+    MatFormFieldModule,

+    MatIconModule,

+    PageHeaderModule,

+    MatSnackBarModule,

+    ScheduleTestModalModule

+  ],

+  declarations: [SchedulingComponent]

+})

+export class SchedulingModule {

+}

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
index 0000000..190e894
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/settings/settings-routing.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { SettingsComponent } from './settings.component';

+

+const routes: Routes = [

+  {

+    path: '',

+    component: SettingsComponent

+  }

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class SettingsRoutingModule { }

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
index 0000000..2cd086b
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/settings/settings.component.pug
@@ -0,0 +1,42 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+    h2 Settings

+    hr

+

+h4(style="padding-top:1em;") Default Group Configuration

+.row

+    .col-sm-5

+        label Enable default group setting? 

+.row

+    .col-sm-5

+        mat-radio-group

+            mat-radio-button(value='enable', style="padding-right:5em;", [checked]="defaultGroupEnabled", (click)="enableDefaultGroup()") Enable

+            mat-radio-button(value='disable', [checked]="!defaultGroupEnabled", (click)="disableDefaultGroup()") Disable

+.row

+    .col-sm-5

+        mat-form-field

+            mat-label(style="color:black") {{ defaultGroup?.groupName }}

+            mat-select([disabled]='!defaultGroupEnabled')

+                mat-option(*ngFor="let group of eligibleGroups", (click)='changDefaultGroup(group)') {{group?.groupName}}

+.row

+    .col-sm-5

+        // button for submitting the information in the form

+        button(mat-raised-button='', color='primary', class='pull-right', (click)='update()') Save

+

+hr

+        
\ 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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/settings/settings.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..a86c4e4
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/settings/settings.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { SettingsComponent } from './settings.component';

+

+describe('SettingsComponent', () => {

+  let component: SettingsComponent;

+  let fixture: ComponentFixture<SettingsComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ SettingsComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(SettingsComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..16312b7
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/settings/settings.component.ts
@@ -0,0 +1,103 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { CookieService } from 'ngx-cookie-service';

+import { UserService } from 'app/shared/services/user.service';

+import { GroupService } from 'app/shared/services/group.service';

+import { routerTransition } from 'app/router.animations';

+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';

+import { Group } from 'app/shared/models/group.model';

+import { MatSnackBar } from '@angular/material';

+

+@Component({

+  selector: 'app-settings',

+  templateUrl: './settings.component.pug',

+  styleUrls: ['./settings.component.scss'],

+  animations: [routerTransition()]

+})

+

+export class SettingsComponent implements OnInit {

+  defaultGroupEnabled = false;

+  private defaultGroup;

+  private eligibleGroups;

+  private currentUser;

+  private defaultGroupId;

+

+  constructor(private cookie: CookieService,

+    private user: UserService,

+    private _group: GroupService,

+    private snack: MatSnackBar

+  ) { }

+

+  ngOnInit() {

+

+    this.currentUser = JSON.parse(this.cookie.get('currentUser'));

+

+    this._group.find({ $limit: -1 }).subscribe((result) => {

+      if (result)

+        this.eligibleGroups = result;

+    });

+

+    this.user.get(this.currentUser._id).subscribe((result) => {

+      if (result)

+        this.defaultGroupId = result['defaultGroup'];

+        this.defaultGroupEnabled = result['defaultGroupEnabled'];

+

+        this._group.get(this.defaultGroupId).subscribe((result) => {

+        this.defaultGroup = result;

+      });

+    });

+  }

+

+  changDefaultGroup(group: Group) {

+    this.defaultGroup = group;

+  }

+

+  enableDefaultGroup() {

+    this.defaultGroupEnabled = true;

+  }

+

+  disableDefaultGroup() {

+    this.defaultGroupEnabled = false;

+    

+  }

+

+  update() {

+

+    this.currentUser.defaultGroupEnabled = this.defaultGroupEnabled;

+    this.currentUser.defaultGroup = this.defaultGroup;

+    this.cookie.set('currentUser', JSON.stringify(this.currentUser));

+    

+

+    

+    let userPatch = {

+      _id: this.currentUser._id,

+      defaultGroup: this.defaultGroup._id,

+      defaultGroupEnabled: this.defaultGroupEnabled

+    };

+

+    this.user.patch(userPatch).subscribe((res) => {

+      let snackMessage = 'Successfully Updated Settings';

+                this.snack.openFromComponent(AlertSnackbarComponent, {

+                    duration: 1500,

+                    data: {

+                        message: snackMessage

+                    }

+                })

+    })

+  }

+}

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
index 0000000..0d694f4
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/settings/settings.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { SettingsModule } from './settings.module';

+

+describe('SettingsModule', () => {

+  let settingsModule: SettingsModule;

+

+  beforeEach(() => {

+    settingsModule = new SettingsModule();

+  });

+

+  it('should create an instance', () => {

+    expect(settingsModule).toBeTruthy();

+  });

+});

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
index 0000000..605aac4
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/settings/settings.module.ts
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { MatSelectModule } from '@angular/material/select';

+import { MatFormFieldModule } from '@angular/material/form-field';

+import { FormsModule, ReactiveFormsModule} from '@angular/forms';

+import { MatCheckboxModule, MatBadgeModule, MatButtonModule, MatCardModule, MatIconModule, MatInputModule, MatRadioModule, MatSnackBarModule} from '@angular/material';

+

+import { SettingsRoutingModule } from './settings-routing.module';

+import { SettingsComponent } from './settings.component';

+import { AlertSnackbarModule } from 'app/shared/modules/alert-snackbar/alert-snackbar.module';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    SettingsRoutingModule,

+    MatSelectModule,

+    MatButtonModule,

+    FormsModule,

+    ReactiveFormsModule,

+    MatBadgeModule,

+    MatCardModule,

+    MatIconModule,

+    MatInputModule,

+    MatFormFieldModule,

+    MatRadioModule,

+    MatSnackBarModule,

+    AlertSnackbarModule,

+    MatCheckboxModule

+  ],

+  declarations: [SettingsComponent]

+})

+export class SettingsModule { }

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
index 0000000..6b0cc0c
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.pug
@@ -0,0 +1,63 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+.row.mt-2

+  .col-sm-4(*ngIf="data")

+    h3 {{ data.testName }}

+    p {{ data.testDescription }}

+    p.text-muted Updated At {{ data.updatedAt }}

+    p.text-muted Created At {{ data.createdAt }}

+  .col-sm-8

+    mat-card.mb-4

+      div(mat-card-image, style="padding: 5% 2px; margin:0px; width:100%; position: relative; cursor: pointer", #canvas, (click)="enlargeBpmn()", [attr.id]="'canvas' + testDefinitionId")

+        button(mat-icon-button, color="primary", style="position: absolute; top: 0px; right: 0px; z-index: 100")

+          mat-icon zoom_in

+

+.row(*ngIf="testInstanceList")

+  .col-12

+    table.mat-elevation-z4(mat-table, [dataSource]="testInstanceList", style="width: 100%")

+

+      ng-container(matColumnDef="name")

+        th(mat-header-cell, *matHeaderCellDef) Instances

+        td(mat-cell, *matCellDef="let element")

+          a([routerLink]="['/test-instances', {filter: element._id}]") {{ element.testInstanceName}}

+

+      ng-container(matColumnDef="{{status}}", *ngFor="let status of statusList")

+        th(mat-header-cell, *matHeaderCellDef) # {{status.toLowerCase()}}

+        td(mat-cell, *matCellDef="let element") 

+          .dropdown(ngbDropdown, placement="top-right", *ngIf="element[status]")

+            a(ngbDropdownToggle) {{ element[status]}}

+            .dropdown-menu(ngbDropdownMenu, style="max-height: 200px; overflow-y: scroll")

+              div(*ngFor="let execution of testExecutionList | filterBy: {testResult: status}")

+                a.dropdown-item([routerLink]="['/control-panel']", [queryParams]="{id: execution._id}", *ngIf="execution.historicTestInstance._id == element._id" )

+                  i.fa.fa-fw.fa-bar-chart(style="color: orange")

+                  span.pl-1 {{execution.startTime}}

+      

+      ng-container(matColumnDef="options", stickyEnd)

+        th.optionsColumn(mat-header-cell, *matHeaderCellDef)

+        td.optionsColumn(mat-cell, *matCellDef="let element") 

+          .dropdown.options(ngbDropdown, placement="left", style="margin-right: -20px")

+            button(mat-icon-button, ngbDropdownToggle) 

+              mat-icon more_vert

+            .dropdown-menu(ngbDropdownMenu)

+              a.dropdown-item((click)='executeTestInstance(element)')

+                i.fa.fa-fw.fa-refresh(style="color: green")

+                span.pl-1 Execute

+

+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")

+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")

+

+    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
index 0000000..3210c46
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.scss
@@ -0,0 +1,27 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+mat-paginator {

+    background-color: transparent;

+}

+

+.options .dropdown-toggle::after {

+    display:none;

+}

+

+.optionsColumn {

+    text-align: right;

+}
\ 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
index 0000000..eb5296c
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestDefinitionExpandedDetailsComponent } from './test-definition-expanded-details.component';

+

+describe('TestDefinitionExpandedDetailsComponent', () => {

+  let component: TestDefinitionExpandedDetailsComponent;

+  let fixture: ComponentFixture<TestDefinitionExpandedDetailsComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestDefinitionExpandedDetailsComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestDefinitionExpandedDetailsComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..4e2891d
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-definition-expanded-details/test-definition-expanded-details.component.ts
@@ -0,0 +1,216 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, ViewChild, HostListener, AfterContentInit, AfterViewInit, ElementRef, OnDestroy } from '@angular/core';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+import { TestInstanceService } from 'app/shared/services/test-instance.service';

+import { MatTableDataSource, MatPaginator, MatDialog, MatSnackBar } from '@angular/material';

+import Modeler from 'bpmn-js';

+import { timeInterval } from 'rxjs/operators';

+import { Observable } from 'rxjs';

+import { TestExecutionService } from 'app/shared/services/test-execution.service';

+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';

+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';

+import { SchedulingService } from 'app/shared/services/scheduling.service';

+import { FileTransferService } from 'app/shared/services/file-transfer.service';

+import { Buffer } from 'buffer';

+import { ViewWorkflowModalComponent } from 'app/shared/modules/view-workflow-modal/view-workflow-modal.component';

+import { ExecuteService } from 'app/shared/services/execute.service';

+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';

+

+@Component({

+  selector: 'app-test-definition-expanded-details',

+  templateUrl: './test-definition-expanded-details.component.pug',

+  styleUrls: ['./test-definition-expanded-details.component.scss']

+})

+export class TestDefinitionExpandedDetailsComponent implements OnInit, OnDestroy {

+

+  @Input() public testDefinitionId;

+

+  @Input() public events: Observable<void>;

+  

+  @ViewChild(MatPaginator) instancePaginator: MatPaginator;

+  @ViewChild('canvas') canvas: ElementRef;

+

+  public data = null;

+  public dataLength = 0;

+  public displayedColumns;

+  public foundStatuses = ['name'];

+  public statusList = ['COMPLETED', 'SUCCESS', 'UNKNOWN', 'FAILURE', 'STOPPED', 'UNAUTHORIZED', 'FAILED'];

+  public testInstanceList = null;

+  public testExecutionList = [];

+  public viewer;

+  public eventSub;

+  public bpmnXml;

+

+  constructor(

+    private bpmnFactory: BpmnFactoryService, 

+    private fileTransfer: FileTransferService, 

+    private dialog: MatDialog, 

+    private testDefinition: TestDefinitionService, 

+    private testInstance: TestInstanceService, 

+    private testExecution: TestExecutionService, 

+    private execute: ExecuteService, 

+    private modal: MatDialog, 

+    private snack: MatSnackBar

+  ) { }

+

+  async ngOnInit() {

+    

+    await this.testDefinition.get(this.testDefinitionId).subscribe(

+      result => {

+        result['createdAt'] = new Date(result['createdAt']).toLocaleString();

+        result['updatedAt'] = new Date(result['updatedAt']).toLocaleString();

+        this.data = result;

+        if(this.data.bpmnInstances){

+          this.bpmnFactory.setup({

+            mode: 'viewer',

+            options: {

+              container: this.canvas.nativeElement

+            },

+            fileId: this.data.bpmnInstances[0].bpmnFileId

+          }).then(res => {

+            this.viewer = res;

+          });

+          // this.loadDiagram();

+        }

+      }

+    );

+

+    this.testInstanceList = new MatTableDataSource();

+    this.testInstance.find({

+      $limit: -1,

+      $sort: {

+        createdAt: -1

+      },

+      testDefinitionId: this.testDefinitionId

+    }).subscribe(

+      result => {

+        this.testInstanceList.data = result;

+        this.testInstanceList.paginator = this.instancePaginator;

+

+        this.testInstanceList.data.forEach(elem => {

+          this.setExecutions(elem._id);

+        });

+

+        this.displayedColumns = ['name', 'COMPLETED', 'SUCCESS', 'UNKNOWN', 'FAILURE', 'STOPPED', 'UNAUTHORIZED', 'FAILED', 'options'];

+

+      }

+    )

+

+    //If parent emeits, diagram will reload

+    if(this.events != undefined && this.events){

+      this.events.subscribe(() => {

+        setTimeout(() => {

+          this.loadDiagram();

+        }, 500)

+      });

+    }

+  }

+

+  enlargeBpmn(){

+    this.dialog.open(ViewWorkflowModalComponent, {

+      data: {

+        xml: this.viewer.getBpmnXml()

+      },

+      width: '100%',

+      height: '100%'

+    })

+  }

+

+  ngOnDestroy() {

+    delete this.events;

+  }

+

+  async setExecutions(instanceId) {

+    // ['$limit=-1', '$sort[startTime]=-1', 'testInstanceId=' + instanceId]

+    this.testExecution.find({

+      $limit: -1,

+      $sort: {

+        startTime: -1

+      },

+      'historicTestInstance._id': instanceId

+    }).subscribe(

+      result => {

+        for(let i = 0; i < result['length']; i++){

+          result[i].startTime = new Date(result[i].startTime).toLocaleString();

+          this.testExecutionList.push(result[i]);

+          for(let j = 0; j < this.testInstanceList.data.length; j++){

+            if(this.testInstanceList.data[j]._id == instanceId){

+              if(!this.testInstanceList.data[j][result[i]['testResult']]){

+                this.testInstanceList.data[j][result[i]['testResult']] = 1;

+              }else{

+                this.testInstanceList.data[j][result[i]['testResult']] += 1;

+              }

+            }

+          }

+        }

+

+        //this.setDisplayColumns();

+        // for(let i = 0; i < result[i]; i++){

+        //   this.testInstanceList[instanceId] = result;

+        // }

+        

+      }

+    );

+  }

+

+  loadDiagram(){

+    if(this.viewer && this.data && this.data.bpmnInstances){

+      this.viewer.renderDiagram();

+    }

+  }

+

+  executeTestInstance(element){

+    (element);

+    if(element.testDefinitionId){

+      const executer = this.modal.open(AlertModalComponent, {

+        width: '250px',

+        data: {

+          type: 'confirmation',

+          message: 'Are you sure you want to run ' + element.testInstanceName + '?'

+        }

+      });

+

+      executer.afterClosed().subscribe(result => {

+        if(result){

+          this.execute.create({

+            _id : element._id,

+            async: true

+          }).subscribe((result) => {

+            this.snack.openFromComponent(AlertSnackbarComponent, {

+              duration: 1500,

+              data: {

+                message: 'Test Instance Executed'

+              }

+            });

+          },

+          (error) => {

+            this.modal.open(AlertModalComponent, {

+              width: '450px',

+              data: {

+                type: 'Alert',

+                message: 'Failed to execute Test Instance!\n' + JSON.stringify(error)

+              }

+            });

+          })

+        }

+      });

+    }

+    

+  }

+

+}

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
index 0000000..b19d3f0
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog-routing.module.ts
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {RouterModule, Routes} from '@angular/router';

+import {TestExecutionsCatalogComponent} from './test-executions-catalog.component';

+

+const routes: Routes = [

+    {

+        path: '',

+        component: TestExecutionsCatalogComponent

+    }

+];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class TestExecutionsCatalogRoutingModule {

+}

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
index 0000000..21663f3
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.pug
@@ -0,0 +1,61 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+  app-page-header([heading]="'Test Executions'", [icon]="'fa-edit'")

+

+  .card-mb-12

+    .pull-left

+      mat-form-field

+        input(matInput, name="filter", (keyup)="applyFilter($event.target.value)", placeholder="Filter")

+    //.pull-right

+      button(mat-raised-button, color="primary", (click)="createTestInstance()") New

+

+    div(style="width: 100%", [hidden]="!loading")

+      mat-spinner(style="margin: auto", color="primary")

+

+    table.mat-elevation-z8(mat-table, [dataSource]="dataSource", style="width: 100%", [hidden]="loading")

+

+      ng-container(matColumnDef="testInstanceName")

+        th(mat-header-cell, *matHeaderCellDef) Test Instance

+        td(mat-cell, *matCellDef="let element") {{ (element.historicTestInstance) ? element.historicTestInstance.testInstanceName : 'Does Not Exist' }}

+

+      ng-container(matColumnDef="testInstanceDescription")

+        th(mat-header-cell, *matHeaderCellDef) Description

+        td(mat-cell, *matCellDef="let element") {{ (element.testInstanceId) ? element.testInstanceId.testInstanceDescription : ''}}

+

+      ng-container(matColumnDef="result")

+        th(mat-header-cell, *matHeaderCellDef) Result

+        td(mat-cell, *matCellDef="let element") {{ element.testResult}}

+

+      ng-container(matColumnDef="totalTime")

+        th(mat-header-cell, *matHeaderCellDef) Total Time

+        td(mat-cell, *matCellDef="let element") {{ element.totalTime + ' secs' }} 

+

+      ng-container(matColumnDef="options")

+        th(mat-header-cell, *matHeaderCellDef) Options

+        td(mat-cell, *matCellDef="let element")

+          button.mr-3(mat-mini-fab, matTooltip="Execution Logs", color="primary", [routerLink]="['/control-panel']", [queryParams]="{id: element._id}")

+            i.fa.fa-bar-chart

+          button.text-white(mat-mini-fab, matTooltip="Delete", color='warn', (click)='deleteTestInstance(element)')

+            i.fa.fa-remove

+          

+

+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")

+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")

+

+    mat-paginator([length]="resultsLength", [pageSizeOptions]="[10, 25, 100]", [hidden]="loading")

+

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
index 0000000..56e842b
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.scss
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.mat-mini-fab{

+    width: 30px !important;

+    height: 30px !important;

+    line-height: 10px !important;

+}

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
index 0000000..6382ba9
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestExecutionsCatalogComponent } from './test-executions-catalog.component';

+

+describe('TestExecutionsCatalogComponent', () => {

+  let component: TestExecutionsCatalogComponent;

+  let fixture: ComponentFixture<TestExecutionsCatalogComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestExecutionsCatalogComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestExecutionsCatalogComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..a054e59
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.component.ts
@@ -0,0 +1,162 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewChild, ViewContainerRef, OnDestroy } from '@angular/core';

+import { MatPaginator, MatDialog, MatTableDataSource, MatSnackBar } from '@angular/material';

+import { Router } from '@angular/router';

+import { ActivatedRoute } from '@angular/router';

+import { ListService } from 'app/shared/services/list.service';

+import { TestInstanceService } from 'app/shared/services/test-instance.service';

+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';

+import { TestExecutionService } from 'app/shared/services/test-execution.service';

+import { routerTransition } from 'app/router.animations';

+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+import { GroupService } from 'app/shared/services/group.service';

+import { Subscription } from 'rxjs';

+

+@Component({

+  selector: 'app-test-executions-catalog',

+  templateUrl: './test-executions-catalog.component.pug',

+  styleUrls: ['./test-executions-catalog.component.scss'],

+  animations: [routerTransition()]

+})

+export class TestExecutionsCatalogComponent implements OnInit, OnDestroy {

+

+  private toDestroy: Array<Subscription> = [];

+  public dataSource;

+  public displayedColumns: string[] = ['testInstanceName', 'testInstanceDescription', 'result', 'totalTime', 'options'];

+  public resultsLength;

+  public loading = false;

+

+  @ViewChild(MatPaginator) paginator: MatPaginator;

+

+  constructor(

+    private list: ListService,

+    private testExecution: TestExecutionService,

+    private modal: MatDialog,

+    private route: ActivatedRoute,

+    private _groups: GroupService,

+    private snack: MatSnackBar

+  ) {

+  }

+

+  ngOnInit() {

+    this.setComponentData(this._groups.getGroup());

+    this.toDestroy.push(this._groups.groupChange().subscribe(group => {

+      this.setComponentData(group);

+    }));

+  }

+

+  ngOnDestroy() {

+    this.toDestroy.forEach(e => e.unsubscribe());

+  }

+

+  setComponentData(group) {

+    if (!group) {

+      return;

+    }

+    this.loading = true;

+

+    this.dataSource = new MatTableDataSource();

+    this.dataSource.paginator = this.paginator;

+

+    //RG: Hard limit returns object, -1 returns array

+    const params = { $limit: 50, groupId: group._id, $populate: ['testInstanceId'], $sort: { startTime: -1 } }//['$limit=-1', '$populate[]=testInstanceId', '$sort[startTime]=-1'];

+    if (this.route.snapshot.params['filter']) {

+      params['testResult'] = this.route.snapshot.params['filter'].toUpperCase();

+    }

+    this.testExecution.find(params).subscribe((response) => {

+

+      let list = response;

+      //RG: check if hard limit if so it will be object w/ prop data

+      if(!Array.isArray(response) && response.hasOwnProperty('data')){

+        list = response['data'];

+      }

+      for (let i = 0; i < list['length']; i++) {

+        const tsDate = new Date(list[i]['startTime']);

+        const teDate = new Date(list[i]['endTime']);

+        list[i]['totalTime'] = (teDate.getTime() - tsDate.getTime()) / 1000;

+      }

+      this.dataSource.data = list;

+      this.resultsLength = this.dataSource.data.length;

+      this.loading = false;

+    });

+

+  }

+

+  applyFilter(filterValue: string) {

+    this.dataSource.filter = filterValue.trim().toLowerCase();

+  }

+

+  createTestInstance() {

+    // const create = this.modal.open(TestDefinition, {

+    //   width: '450px',

+    //   data: {

+    //     goal: 'create'

+    //   }

+    // });

+

+    // create.afterClosed().subscribe(result => {

+    //   this.list.listMap['vth'].currentList.subscribe(x => {

+    //     this.dataSource = x;

+    //   });

+    // });

+  }

+

+

+  editTestInstance(th) {

+    // const edit = this.modal.open(TestHeadModalComponent, {

+    //   width: '450px',

+    //   data: {

+    //     goal: 'edit',

+    //     testHead: th

+    //   }

+    // });

+

+    // edit.afterClosed().subscribe(result => {

+    //   console.log(result);

+    // });

+  }

+

+  deleteTestInstance(te) {

+    const deleter = this.modal.open(AlertModalComponent, {

+      width: '250px',

+      data: {

+        type: 'confirmation',

+        message: 'Are you sure you want to delete ' + te.testExecutionName + ' ?'

+      }

+    });

+

+    deleter.afterClosed().subscribe(result => {

+      if (result) {

+        this.testExecution.delete(te._id).subscribe(response => {

+          this.snack.openFromComponent(AlertSnackbarComponent, {

+            duration: 1500,

+            data: {

+              message: 'Test Execution Deleted'

+            }

+          });

+          this.list.removeElement('te', '_id', te._id + '');

+          this.list.listMap['te'].currentList.subscribe(x => {

+            this.dataSource.data = x;

+            this.resultsLength = x.length;

+          });

+        });

+      }

+    });

+  }

+}

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
index 0000000..e977dad
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {TestExecutionsCatalogModule} from './test-executions-catalog.module';

+

+describe('TestExecutionsCatalogModule', () => {

+    let testExecutionsCatalogModule: TestExecutionsCatalogModule;

+

+    beforeEach(() => {

+        testExecutionsCatalogModule = new TestExecutionsCatalogModule();

+    });

+

+    it('should create an instance', () => {

+        expect(testExecutionsCatalogModule).toBeTruthy();

+    });

+});

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
index 0000000..cc2d6cd
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-executions-catalog/test-executions-catalog.module.ts
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {CommonModule} from '@angular/common';

+

+import {TestExecutionsCatalogRoutingModule} from './test-executions-catalog-routing.module';

+import {TestExecutionsCatalogComponent} from './test-executions-catalog.component';

+import {PageHeaderModule} from 'app/shared';

+import {FormsModule} from '@angular/forms';

+import {FilterPipeModule} from 'ngx-filter-pipe';

+import {

+    MatButtonModule,

+    MatFormFieldModule,

+    MatIconModule,

+    MatInputModule,

+    MatPaginatorModule,

+    MatTableModule,

+    MatTooltipModule,

+    MatSnackBarModule,

+    MatProgressSpinnerModule

+} from '@angular/material';

+import {TestHeadModalModule} from 'app/shared/modules/test-head-modal/test-head-modal.module';

+import {AlertModalModule} from 'app/shared/modules/alert-modal/alert-modal.module';

+

+@NgModule({

+    imports: [

+        CommonModule,

+        TestExecutionsCatalogRoutingModule,

+        PageHeaderModule,

+        FormsModule,

+        FilterPipeModule,

+        MatButtonModule,

+        MatTableModule,

+        MatFormFieldModule,

+        MatInputModule,

+        MatPaginatorModule,

+        TestHeadModalModule,

+        AlertModalModule,

+        MatIconModule,

+        MatTooltipModule,

+        MatSnackBarModule,

+        MatProgressSpinnerModule

+    ],

+    declarations: [TestExecutionsCatalogComponent]

+})

+export class TestExecutionsCatalogModule {

+}

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
index 0000000..c9c9575
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.pug
@@ -0,0 +1,28 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+.col-12(style="background-color: #f5f5f5; padding: 10px") 

+  mat-spinner(*ngIf="isLoading", [diameter]="25", style="margin: auto")

+  div(*ngIf="executionList.length > 0")

+    .list-group(*ngFor="let execution of executionList")

+      a.list-group-item.list-group-item-action(style="", [routerLink]="['/control-panel']", [queryParams]="{id: execution._id}") 

+        .pull-left

+          i.fa.fa-fw.fa-bar-chart(style="color: orange") 

+          |  {{execution.startTime}}

+        .pull-right

+          div([attr.class]="execution.testResult + '-dash'") {{execution.testResult}}

+  div(*ngIf="!isLoading && executionList.length == 0", style="text-align:center")

+    p There are no executions for this instance.

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
index 0000000..17106e6
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.scss
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.COMPLETED-dash {

+    color: #0d47a1;

+}

+

+.SUCCESS-dash {

+    color: #199700;

+}

+

+.FAILURE-dash {

+    color: #dd2c00;

+}

+

+.STOPPED-dash {

+    color: #ff9100;

+}

+

+.UNAUTHORIZED-dash {

+    color: #000000;

+}

+

+.UNKNOWN-dash {

+    color: grey;

+}
\ 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
index 0000000..4828eed
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestInstanceExpandedDetailsComponent } from './test-instance-expanded-details.component';

+

+describe('TestInstanceExpandedDetailsComponent', () => {

+  let component: TestInstanceExpandedDetailsComponent;

+  let fixture: ComponentFixture<TestInstanceExpandedDetailsComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestInstanceExpandedDetailsComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestInstanceExpandedDetailsComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..baf9c40
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instance-expanded-details/test-instance-expanded-details.component.ts
@@ -0,0 +1,59 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input } from '@angular/core';

+import { TestExecutionService } from 'app/shared/services/test-execution.service';

+

+@Component({

+  selector: 'app-test-instance-expanded-details',

+  templateUrl: './test-instance-expanded-details.component.pug',

+  styleUrls: ['./test-instance-expanded-details.component.scss']

+})

+export class TestInstanceExpandedDetailsComponent implements OnInit {

+

+  @Input() public testInstanceId;

+  public executionList:any = [];

+  public isLoading = true;

+

+  constructor(private testexecution: TestExecutionService) { }

+

+  ngOnInit() {

+    this.testexecution.find({

+      $limit: 100, 

+      $sort: { 

+        startTime: -1 

+      }, 

+      $or: [

+        { "historicTestInstance._id": this.testInstanceId},

+        { testInstanceId: this.testInstanceId }

+      ],

+      $select: ['startTime', 'testResult']

+      

+    }).subscribe(

+      result => {

+        for(let i = 0; i < result['data']['length']; i++){

+          result['data'][i]['startTime'] = new Date(result['data'][i]['startTime']).toLocaleString();

+        }

+        this.executionList = result['data'];

+        this.isLoading = false;

+      }, 

+      err => {

+        this.isLoading = false;

+      }

+    );

+  }

+

+}

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
index 0000000..69d5e33
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog-routing.module.ts
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {RouterModule, Routes} from '@angular/router';

+import {TestInstancesCatalogComponent} from './test-instances-catalog.component';

+

+const routes: Routes = [{

+    path: '', component: TestInstancesCatalogComponent

+}];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class TestInstancesCatalogRoutingModule {

+}

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
index 0000000..446c892
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.pug
@@ -0,0 +1,72 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition]).mb-3

+  

+  .row

+    .col

+      app-page-header.pull-left([heading]="'Test Instances'", [icon]="'fa-edit'")

+      button.mr-2.pull-right(mat-raised-button, color="primary", (click)="createTestInstance()") New

+

+

+

+  .row

+    .col.mt-2

+      //- Delete

+      button.mr-2.pull-right(color="primary", matTooltip="Delete Test Instance", mat-icon-button, (click)="deleteMultipleTestInstance()", [disabled]="(!hasSelectedRows)") 

+        mat-icon delete_forever

+      //- Clone

+      button.mr-2.pull-right(color="primary", matTooltip="Clone Test Instance", mat-icon-button, (click)="cloneTestInstance()", [disabled]="(!selectedSingleRow)") 

+        mat-icon insert_drive_file

+      //- Edit

+      button.mr-2.pull-right(color="primary", matTooltip="Edit Test Instance", mat-icon-button, (click)="editTestInstance()", [disabled]="(!selectedSingleRow)")

+        mat-icon edit

+      //- Execute

+      button.mr-2.pull-right(color="primary", matTooltip="Execute Test Instance", mat-icon-button, (click)="executeMultipleTestInstance()", *ngIf="(selectedUnlockedRows)")

+        mat-icon play_circle_outline

+      //- Schedule

+      button.mr-2.pull-right(color="primary", matTooltip="Schedule Test Instance", mat-icon-button, (click)="schedule()", *ngIf="(selectedUnlockedRows && selectedSingleRow)")

+        mat-icon date_range

+

+

+  .row

+    .col-md

+      ag-grid-angular.ag-theme-material(

+        style="width:100%; height: 600px",

+        [rowData]="rowData",

+        [columnDefs]="columnDefs",

+        rowSelection="multiple",

+        [rowMultiSelectWithClick]="true",

+        (rowSelected)="onRowSelected($event)",

+        (gridReady)="onGridReady($event)", 

+        [singleClickEdit]="true",

+        [gridOptions]="gridOptions",

+        (rowDataChanged)="selectActiveInstance($event)"

+        )

+

+    .col-md-3(*ngIf="selectedSingleRow")

+      h1 Executions

+      div(*ngFor = "let ti of rowData")

+        app-test-instance-expanded-details(*ngIf="ti._id == selectedRows[0]._id", [testInstanceId]="selectedRows[0]._id")

+

+

+        

+

+   

+

+

+

+

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
index 0000000..110ce5b
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.scss
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.mat-mini-fab{

+    width: 30px !important;

+    height: 30px !important;

+    line-height: 10px !important;

+}

+.dropdown-menu{

+  z-index: 10;

+}

+.dropdown-toggle::after {

+    display:none;

+}

+

+tr.example-detail-row {

+    height: 0;

+  }

+  

+  tr.example-element-row:not(.example-expanded-row):hover {

+    background: #f5f5f5;

+    cursor: pointer;

+  }

+  

+  tr.example-element-row:not(.example-expanded-row):active {

+    background: #efefef;

+    cursor: pointer;

+  }

+

+  .example-element-row td {

+    border-bottom-width: 0;

+  }

+

+  table {

+    width: 100%;

+  }

+

+  .options {

+    flex: 0 0 10px !important;

+  }
\ 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
index 0000000..11ccb4e
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.spec.ts
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {async, ComponentFixture, TestBed} from '@angular/core/testing';

+

+import {TestInstancesCatalogComponent} from './test-instances-catalog.component';

+

+describe('TestInstancesCatalogComponent', () => {

+    let component: TestInstancesCatalogComponent;

+    let fixture: ComponentFixture<TestInstancesCatalogComponent>;

+

+    beforeEach(async(() => {

+        TestBed.configureTestingModule({

+            declarations: [TestInstancesCatalogComponent]

+        }).compileComponents()

+            .then((arg) => {

+                // handle

+            })

+            .catch((err) => {

+                // handle error

+            });

+    }));

+

+    beforeEach(() => {

+        fixture = TestBed.createComponent(TestInstancesCatalogComponent);

+        component = fixture.componentInstance;

+        fixture.detectChanges();

+    });

+

+    it('should create', () => {

+        expect(component).toBeTruthy();

+    });

+});

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
index 0000000..cb5a6e5
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.component.ts
@@ -0,0 +1,436 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewChild, ViewContainerRef, AfterViewInit, OnDestroy, ViewChildren, ElementRef, QueryList } from '@angular/core';

+import { MatDialog, MatPaginator, MatSnackBar, MatTableDataSource, MatListItem } from '@angular/material';

+import { ActivatedRoute, Router } from '@angular/router';

+import { ListService } from '../../shared/services/list.service';

+import { AlertModalComponent } from '../../shared/modules/alert-modal/alert-modal.component';

+import { TestInstanceService } from '../../shared/services/test-instance.service';

+import { routerTransition } from 'app/router.animations';

+import { SchedulingService } from '../../shared/services/scheduling.service';

+import { TestInstanceModalComponent } from '../../shared/modules/test-instance-modal/test-instance-modal.component';

+import { AlertSnackbarComponent } from '../../shared/modules/alert-snackbar/alert-snackbar.component';

+import { ScheduleTestModalComponent } from '../../shared/modules/schedule-test-modal/schedule-test-modal.component';

+import { animate, state, style, transition, trigger } from '@angular/animations';

+import { TestDefinitionService } from '../../shared/services/test-definition.service';

+import { Observable, Subscription } from 'rxjs';

+import { ExecuteService } from 'app/shared/services/execute.service';

+import { GroupService } from 'app/shared/services/group.service';

+import { GridOptions } from "ag-grid-community";

+

+

+@Component({

+    selector: 'app-test-instances-catalog',

+    templateUrl: './test-instances-catalog.component.pug',

+    styleUrls: ['./test-instances-catalog.component.scss'],

+    animations: [routerTransition(),

+    trigger('detailExpand', [

+        state('collapsed', style({ height: '0px', minHeight: '0', display: 'none' })),

+        state('expanded', style({ height: '*' })),

+        transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),

+    ])

+    ]

+})

+export class TestInstancesCatalogComponent implements OnInit, AfterViewInit, OnDestroy {

+

+

+    public resultsLength;

+    public loading = false;

+

+

+    public columnDefs = [

+        {headerName: '', field: 'disabled', cellRenderer: this.disabledIndicator, width: 100, hide: false, checkboxSelection:true, headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true},

+        {headerName: 'Name', field: 'testInstanceName', sortable: true, filter: true, resizable: true, width: 300},

+        {headerName: 'Description', field: 'testInstanceDescription', sortable: true, filter: true, resizable: true, width: 100},

+        {headerName: 'Id', field: '_id', sortable: true, filter: true, resizable: true, width: 200, editable: true},

+        {headerName: 'Test Definition', field: 'testDefinitionId.testName', sortable: true, filter: true, resizable: true}

+        

+      ];

+

+    public selectedRows;

+    public hasSelectedRows;

+    public selectedSingleRow;

+    private gridApi;

+    private gridColumnApi;

+    public selectedUnlockedRows;

+    public rowData;

+

+    public gridOptions: GridOptions;

+

+    

+    public params;

+

+    private subscriptions: Subscription[] = [];

+

+    @ViewChild(MatPaginator) paginator: MatPaginator;

+

+    constructor(

+

+        private router: Router,

+        private viewRef: ViewContainerRef,

+        private testInstance: TestInstanceService,

+        private modal: MatDialog,

+        private schedulingService: SchedulingService,

+        private snack: MatSnackBar,

+        private route: ActivatedRoute,

+        private testDefinitionService: TestDefinitionService,

+        private _execute: ExecuteService,

+        private _groups: GroupService

+    ) {  }

+

+    ngOnInit() {

+        

+        this.setComponentData(this._groups.getGroup());

+

+        this.subscriptions.push(this._groups.groupChange().subscribe(group => {

+            this.setComponentData(group);

+            

+        }));

+

+        // this.subscriptions.push(this._groups.groupChange().subscribe( group => {

+        //     if(!group["_id"]){

+        //         this.setComponentData(this._groups.getGroup());

+        //     }

+        //     this.setComponentData(group);

+        // }));

+

+

+        this.route.queryParams.subscribe( params => {

+           

+            this.params = params;

+

+

+        });

+

+

+    }

+

+    setComponentData(group) {

+

+        if(!group){

+            return;

+        }

+        this.loading = true;

+        let params = {

+            groupId: group['_id'],

+            $limit: -1,

+            $populate: ['testDefinitionId'],

+            $sort: {

+                createdAt: -1

+            },

+            $select: ['testInstanceName', 'testInstanceDescription', 'testDefinitionId.testName', 'disabled']

+        }

+

+        if (this.route.snapshot.params['filter']) {

+            params['_id'] = this.route.snapshot.params['filter'];

+        }

+

+        this.testInstance.find(params).subscribe((list) => {

+            this.resultsLength = list['length'];

+            this.loading = false;

+            this.rowData = list;

+        },

+        err => {

+            console.log(err);

+        });

+        

+    }

+

+    ngAfterViewInit() {

+   

+    }

+

+    ngOnDestroy() {

+        this.subscriptions.forEach(e => e.unsubscribe());

+    }

+

+

+    schedule() {

+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName, testDefinitionId}) => ({_id, testInstanceName, testDefinitionId}));

+        console.log("The new element is: "+JSON.stringify(this.selectedRows[0]._id));

+        

+        console.log("Here is the selected Row: "+JSON.stringify(this.gridApi.getSelectedRows()[0]));

+        const dialogRef = this.modal.open(ScheduleTestModalComponent, {

+            width: '90%',

+            data: {

+                id: this.selectedRows[0]._id

+            }

+        });

+

+        dialogRef.afterClosed().subscribe(result => {

+            /*if(result != ''){

+              this.test_instance_selected = result;

+              this.strategy_selected = true;

+            }else{

+              this.strategy_selected = false;

+            }*/

+        });

+    }

+

+    executeMultipleTestInstance(){

+        for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){

+            this.executeTestInstance(i);

+        }

+    }

+

+    executeTestInstance(ti) {

+        

+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName, testDefinitionId}) => ({_id, testInstanceName, testDefinitionId}));

+

+

+        if (this.selectedRows[ti].testDefinitionId) {

+            const executer = this.modal.open(AlertModalComponent, {

+                width: '250px',

+                data: {

+                    type: 'confirmation',

+                    message: 'Are you sure you want to run ' + this.selectedRows[ti].testInstanceName + '?'

+                }

+            });

+

+            executer.afterClosed().subscribe(result => {

+                if (result) {

+                    this._execute.create({

+                        _id: this.selectedRows[ti]._id,

+                        async: true

+                    }).subscribe((result) => {

+                        console.log(result);

+                        if (result) {

+                            this.snack.openFromComponent(AlertSnackbarComponent, {

+                                duration: 1500,

+                                data: {

+                                    message: 'Test Instance Executed'

+                                }

+                            });

+                        }

+                    },

+                        (error) => {

+                            console.log(error);

+                            this.modal.open(AlertModalComponent, {

+                                width: '450px',

+                                data: {

+                                    type: 'Alert',

+                                    message: 'Failed to execute Test Instance!\n' + JSON.stringify(error)

+                                }

+                            });

+                        })

+                }

+            });

+        }

+

+    }

+

+    createTestInstance() {

+        const create = this.modal.open(TestInstanceModalComponent, {

+            width: '90%',

+            data: null,

+            disableClose: true

+        });

+

+        create.afterClosed().subscribe(result => {

+          this.ngOnInit();

+        });

+    }

+

+

+    editTestInstance() {

+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName}) => ({_id, testInstanceName}));

+

+        const edit = this.modal.open(TestInstanceModalComponent, {

+            data: {

+                ti: this.selectedRows[0]._id,

+                isEdit: true

+            },

+            width: '90%',

+            disableClose: true

+        });

+

+        edit.afterClosed().subscribe(result => {

+

+        });

+    }

+

+    cloneTestInstance() {

+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName}) => ({_id, testInstanceName}));

+        this.testInstance.get(this.selectedRows[0]._id).subscribe(

+            result => {

+                var temp = Object.assign({}, result);

+                delete result['_id'];

+                delete result['createdAt'];

+                delete result['updatedAt'];

+                if (this.selectedRows[0].testInstanceName) {

+                    result['testInstanceName'] = this.selectedRows[0].testInstanceName + '_Clone';

+                } else {

+                    result['testInstanceName'] = result['testInstanceName'] + '_Clone';

+                }

+                this.testInstance.create(result).subscribe(

+                    resp => {

+                        //this.editTestInstance(resp);

+                        this.editTestInstance();

+                    },

+                    err => {

+                        if (err) {

+                      

+                            result['_id'] = temp['_id'];

+                           

+                            //this.cloneTestInstance(result);

+                        }

+                    }

+                );

+            }

+        )

+    }

+

+    deleteMultipleTestInstance(){

+        for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){

+            this.deleteTestInstance(i);

+        }

+    }

+

+    deleteTestInstance(ti) {

+        this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testInstanceName}) => ({_id, testInstanceName}));

+

+

+        const deleter = this.modal.open(AlertModalComponent, {

+            width: '250px',

+            data: {

+                type: 'confirmation',

+                message: 'Are you sure you want to delete ' + this.selectedRows[ti].testInstanceName + ' ? Executions of this instance will no longer display everything.'

+            }

+        });

+

+        deleter.afterClosed().subscribe(result => {

+            if (result) {

+                this.testInstance.delete(this.selectedRows[ti]._id).subscribe(response => {

+                    this.snack.openFromComponent(AlertSnackbarComponent, {

+                        duration: 1500,

+                        data: {

+                            message: 'Test Instance Deleted'

+                        }

+                    });

+

+                });

+                this.setComponentData(this._groups.getGroup());

+            }

+        });

+        

+    }

+

+

+    disabledIndicator(params){

+        if (params.value){

+          return `<mat-icon class="mat-icon mat-icon-no-color" role="img" >

+           locked</mat-icon>`;   

+    }

+

+ 

+    }

+

+    setParams(element) {

+   

+        if (JSON.stringify(element) == JSON.stringify({testInstanceId: this.params.testInstanceId})){

+            element = {};

+        }

+

+        

+        this.router.navigate([], {

+           //queryParams: {testInstanceId: element.testInstanceId, page: this.paginator.pageIndex, instancePerPage: this.paginator.pageSize }

+            queryParams: {testInstanceId: element._id}

+        })

+           

+    }

+

+    onGridReady(params){

+        this.gridApi = params.api;

+        console.log(params.columnApi.autoSizeColumns)

+        this.gridColumnApi = params.columnApi;

+    

+        //auto size the column widths

+        this.gridColumnApi.autoSizeColumns(['name']);

+

+      }

+

+      selectActiveInstance($event){

+        if(this.params.testInstanceId)

+        {

+            this.gridApi.forEachNode( (node, index) => {

+

+                if(node.data._id ==this.params.testInstanceId)

+                {

+                    // Pre selects the row that was last selected when on the page

+                    node.setSelected(true, true);

+                    //Vertically scrolls to that row so it is visible

+                    this.gridApi.ensureIndexVisible(index, "middle");

+                    

+                }

+              

+            });

+        }

+        

+      }

+

+

+    onRowSelected(event){

+

+        this.selectedRows = this.gridApi.getSelectedRows().map(({ _id, disabled, testInstanceName  }) => ({ _id, disabled, testInstanceName}));

+

+

+        

+        if(event.api.getSelectedNodes().length > 0){

+          this.hasSelectedRows = true;

+          

+            //Checks for all Unlocked rows

+            for (let i = 0; i < event.api.getSelectedNodes().length; i++ )

+            {

+                if(!this.selectedRows[i].disabled)

+                {

+                    this.selectedUnlockedRows = true;

+                }

+                else{

+                    this.selectedUnlockedRows = false;

+                    break;

+                }

+            }

+        }

+        else{

+          this.hasSelectedRows = false;

+          this.selectedUnlockedRows = false;

+          

+          this.setParams({_id: null});

+        }

+

+        

+        //One Row was selected

+        if((event.api.getSelectedNodes().length == 1)){

+          this.selectedSingleRow = true;

+         

+          this.setParams({_id: this.selectedRows[0]._id});

+         

+        }else{

+          this.selectedSingleRow = false;

+          this.setParams({_id: null});

+        }

+       

+    }

+

+

+

+      

+

+

+

+    

+}

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
index 0000000..5823da5
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {TestInstancesCatalogModule} from './test-instances-catalog.module';

+

+describe('TestInstancesCatalogModule', () => {

+    let testInstancesCatalogModule: TestInstancesCatalogModule;

+

+    beforeEach(() => {

+        testInstancesCatalogModule = new TestInstancesCatalogModule();

+    });

+

+    it('should create an instance', () => {

+        expect(testInstancesCatalogModule).toBeTruthy();

+    });

+});

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
index 0000000..6d85016
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/test-instances-catalog/test-instances-catalog.module.ts
@@ -0,0 +1,65 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {CommonModule} from '@angular/common';

+import {TestInstancesCatalogRoutingModule} from './test-instances-catalog-routing.module';

+import {TestInstancesCatalogComponent} from './test-instances-catalog.component';

+import {AlertModalModule} from '../../shared/modules/alert-modal/alert-modal.module';

+import {PageHeaderModule} from '../../shared';

+import {FormsModule} from '@angular/forms';

+import {FilterPipeModule} from 'ngx-filter-pipe';

+import {MatButtonModule, MatFormFieldModule, MatInputModule, MatPaginatorModule, MatTableModule, MatSnackBarModule, MatTooltipModule, MatIconModule} from '@angular/material';

+import {TestHeadModalModule} from '../../shared/modules/test-head-modal/test-head-modal.module';

+import { AlertSnackbarModule } from 'app/shared/modules/alert-snackbar/alert-snackbar.module';

+import {TestInstanceModalModule} from '../../shared/modules/test-instance-modal/test-instance-modal.module';

+import { ScheduleTestModalModule } from '../../shared/modules/schedule-test-modal/schedule-test-modal.module';

+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

+import { TestInstanceExpandedDetailsComponent } from 'app/layout/test-instance-expanded-details/test-instance-expanded-details.component';

+import { AgGridModule } from 'ag-grid-angular';

+

+@NgModule({

+    imports: [

+        CommonModule,

+        TestInstancesCatalogRoutingModule,

+        PageHeaderModule,

+        ScheduleTestModalModule,

+        FormsModule,

+        FilterPipeModule,

+        MatButtonModule,

+        MatTableModule,

+        MatFormFieldModule,

+        MatInputModule,

+        MatPaginatorModule,

+        TestHeadModalModule,

+        MatSnackBarModule,

+        AlertModalModule,

+        MatSnackBarModule,

+        AlertSnackbarModule,

+        TestInstanceModalModule,

+        MatTooltipModule,

+        MatIconModule,

+        NgbModule,

+        MatProgressSpinnerModule,

+        AgGridModule.withComponents([])

+    ],

+    declarations: [TestInstancesCatalogComponent,

+        TestInstanceExpandedDetailsComponent],

+    entryComponents: [TestInstanceExpandedDetailsComponent]

+})

+export class TestInstancesCatalogModule {

+}

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
index 0000000..87d49a5
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.pug
@@ -0,0 +1,64 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+mat-card.mb-3

+    mat-card-header 

+        mat-card-title 

+            h4(*ngIf="testDefinition?.testName") {{ testDefinition.testName }} 

+        mat-card-subtitle(style="margin-bottom: 0px")

+            div(*ngIf="testDefinition?.testDescription") {{testDefinition.testDescription }}

+    mat-card-content

+        .row(*ngIf="testDefinition")

+            .col-sm

+                mat-form-field(*ngIf="testDefinition?.processDefinitionKey")

+                    input(matInput, placeholder="Process Definition Key", type="text", [value]="testDefinition.processDefinitionKey", disabled, name="defKey")

+            .col-sm

+                mat-form-field(*ngIf="testDefinition?.disabled != undefined") 

+                    input(matInput, placeholder="Is Disabled", type="text", [value]="testDefinition.disabled", disabled, name="disabled")

+            .col-sm

+                mat-form-field(*ngIf="testDefinition") 

+                    input(matInput, placeholder="Number Of Versions", type="text", [value]="numOfVersions", disabled, name="numOfVersions")

+            .col-sm

+                mat-form-field(*ngIf="testDefinition?.groupId")

+                    input(matInput, placeholder="Group Id", type="text", [value]="testDefinition.groupId", disabled, name="group")

+            //- .col-sm

+            //-     mat-form-field(style="width:50px",*ngIf="testDefinition?.isPublic")

+            //-         input(matInput, placeholder="Is Public", type="text", [value]="testDefinition.isPublic", disabled, name="public")

+

+div(style="position: relative")

+  .row

+    .col-12

+      .pull-left

+        mat-form-field(style="width:110px")

+          input(matInput, [matDatepicker]="fromPicker", placeholder="From Date", [(ngModel)]="stats.filters.startDate")

+          mat-datepicker-toggle(matSuffix, [for]="fromPicker")

+          mat-datepicker(#fromPicker)

+        mat-form-field.ml-2(style="width:110px")

+          input(matInput, [matDatepicker]="toPicker", placeholder="To Date", [(ngModel)]="stats.filters.endDate")

+          mat-datepicker-toggle(matSuffix, [for]="toPicker")

+          mat-datepicker(#toPicker)

+        button.ml-2(mat-icon-button, (click)="getData()") 

+          mat-icon arrow_forward

+          

+      .pull-right

+        mat-form-field

+          input(matInput, [ngModel]="stats.executionList?.length", placeholder="Total Executions", disabled)

+

+  .row

+    .col-12

+      mat-card

+        mat-card-content

+          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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..24aaec8
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestDefinitionDetailsComponent } from './test-definition-details.component';

+

+describe('TestDefinitionDetailsComponent', () => {

+  let component: TestDefinitionDetailsComponent;

+  let fixture: ComponentFixture<TestDefinitionDetailsComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestDefinitionDetailsComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestDefinitionDetailsComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..660fa62
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/test-definition-details/test-definition-details.component.ts
@@ -0,0 +1,107 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, OnDestroy } from '@angular/core';

+import { ActivatedRoute } from '@angular/router';

+import { Subscription } from 'rxjs';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+import { TestDefinition } from 'app/shared/models/test-definition.model';

+import { StatsService } from 'app/layout/components/stats/stats.service';

+

+@Component({

+  selector: 'app-test-definition-details',

+  templateUrl: './test-definition-details.component.pug',

+  styleUrls: ['./test-definition-details.component.scss']

+})

+export class TestDefinitionDetailsComponent implements OnInit, OnDestroy {

+

+  private toDestroy: Array<Subscription> = [];

+

+  public testDefinition: TestDefinition;

+  

+  constructor(

+    private route: ActivatedRoute, 

+    private _testDefinition: TestDefinitionService,

+    public stats: StatsService

+  ) { }

+

+  ngOnInit() {

+    this.toDestroy.push(this.route.params.subscribe(params => {

+      

+      if(params.id){

+        this._testDefinition.get(params.id).subscribe(

+          res => {

+            

+            this.testDefinition = res as TestDefinition;

+          },

+          err => {

+            

+          })

+

+        this.getData(params.id);

+      }

+    }));

+  }

+

+  get numOfVersions(){

+    if(this.testDefinition['bpmnInstances']){

+      return this.testDefinition['bpmnInstances'].length;

+    }

+    return 0;

+  }

+

+  ngOnDestroy() {

+    this.toDestroy.forEach(elem => elem.unsubscribe());

+  }

+

+  getData(testDefinitionId?){

+    if(!testDefinitionId){

+      testDefinitionId = this.testDefinition._id

+    }

+

+    if(!testDefinitionId){

+      return;

+    }

+

+    this.stats.getDefaultData(1, {

+      'historicTestDefinition._id': testDefinitionId,

+      $select: [

+        'startTime',

+        'endTime',

+        "historicTestDefinition._id",

+        "historicTestDefinition.testName",

+        "historicTestInstance._id",

+        "historicTestInstance.testInstanceName",

+        "testHeadResults.startTime",

+        "testHeadResults.endTime",

+        "testHeadResults.testHeadName",

+        "testHeadResults.testHeadId",

+        "testHeadResults.testHeadGroupId",

+        "testHeadResults.statusCode",

+        'testResult'

+      ],

+      $limit: -1,

+      $sort: {

+        startTime: 1

+      },

+      startTime: {

+        $gte: this.stats.filters.startDate,

+        $lte: this.stats.filters.endDate

+      }

+    });

+  }

+

+}

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
index 0000000..e434133
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/tests-routing.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { TestsComponent } from './tests.component';

+import { TestDefinitionDetailsComponent } from './test-definition-details/test-definition-details.component';

+

+const routes: Routes = [

+  { path:'', component: TestsComponent },

+  { path:':id', component: TestDefinitionDetailsComponent}

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class TestsRoutingModule { }

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
index 0000000..a8fc774
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/tests.component.pug
@@ -0,0 +1,180 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+

+  .row

+    .col

+      .pull-left

+        app-page-header([heading]="'Test Definitions'", [icon]="'fa-edit'")

+      .pull-right

+        button(mat-raised-button, color="primary", (click)="create()") New

+  //-.card-mb-12

+    .pull-left

+      mat-form-field

+        input(matInput, name="filter", (keyup)="applyFilter($event.target.value)", placeholder="Filter")

+    .pull-right

+      button(mat-raised-button, color="primary", (click)="create()") New

+

+    div(style="width: 100%", [hidden]="!loading")

+      mat-spinner(style="margin: auto", color="primary")

+    //- 

+     table.mat-elevation-z8(mat-table, *ngIf="dataSource && dataSource.data && dataSource.data.length > 0", [dataSource]="dataSource", style="width: 100%", [hidden]="loading")

+    

+      ng-container(matColumnDef="lock")

+        th(mat-header-cell, *matHeaderCellDef) 

+        td(mat-cell, *matCellDef="let element", (click)="expand(element)")

+          div.mr-4

+            i.fa.fa-lock(*ngIf="element.disabled") 

+

+      ng-container(matColumnDef="name")

+        th(mat-header-cell, *matHeaderCellDef) Name

+        td(mat-cell, *matCellDef="let element", (dblclick)="navToDefinition(element._id)") {{ element.testName }}

+

+      ng-container(matColumnDef="description")

+        th(mat-header-cell, *matHeaderCellDef) Description

+        td(mat-cell, *matCellDef="let element", (dblclick)="navToDefinition(element._id)") {{ element.testDescription }}

+

+      ng-container(matColumnDef="id")

+        th(mat-header-cell, *matHeaderCellDef) Id

+        td(mat-cell, *matCellDef="let element", (dblclick)="navToDefinition(element._id)") {{ element._id }}

+

+      ng-container(matColumnDef="processDefinitionKey")

+        th(mat-header-cell, *matHeaderCellDef) Process Definition Key

+        td(mat-cell, *matCellDef="let element", (dblclick)="navToDefinition(element._id)") {{ element.processDefinitionKey }}

+

+      ng-container(matColumnDef="options")

+        th(mat-header-cell, *matHeaderCellDef) Options

+        td(mat-cell, *matCellDef="let element")

+          .dropdown(ngbDropdown, placement="bottom-right")

+            button(mat-mini-fab, color="primary", ngbDropdownToggle)

+              i.fa.fa-caret-down

+              //mat-icon more_vert

+            .dropdown-menu(ngbDropdownMenu)

+              a.dropdown-item(*ngIf="isDeployed(element) && !element.disabled", (click)='createInstance(element)')

+                i.fa.fa-fw.fa-plus(style="color: #005000")

+                span.pl-1 Create Instance

+              //- a.dropdown-item((click)='view(element)')

+              //-   i.fa.fa-fw.fa-eye(style="color: #ff9100")

+              //-   span.pl-1 View

+              a.dropdown-item(*ngIf="element.disabled", (click)='unlock(element)')

+                i.fa.fa-fw.far.fa-unlock(style="color: black")

+                span.pl-1 Unlock

+              a.dropdown-item(*ngIf="!element.disabled", (click)='lock(element)')

+                i.fa.fa-fw.far.fa-lock(style="color: black")

+                span.pl-1 Lock

+              a.dropdown-item(*ngIf="favorites.indexOf(element._id) < 0", (click)='favorite(element)')

+                i.fa.fa-fw.far.fa-star(style="color: yellow")

+                span.pl-1 Favorite

+              a.dropdown-item(*ngIf="favorites.indexOf(element._id) >= 0", (click)='unfavorite(element)')

+                i.fa.fa-fw.fas.fa-star(style="color: yellow")

+                span.pl-1 Unfavorite

+              a.dropdown-item((click)='edit(element)')

+                i.fa.fa-fw.fa-pencil(style="color: #0d47a1")

+                span.pl-1 Edit

+              a.dropdown-item([routerLink]="['/modeler']", [queryParams]="{testDefinitionId: element._id}")

+                i.fa.fa-fw.bpmn-icon-bpmn-io(style="color: green")

+                span.pl-1 Modeler

+              a.dropdown-item((click)='delete(element)')

+                i.fa.fa-fw.fa-remove(style="color: #dd2c00")

+                span.pl-1 Delete

+          //- button.mr-3(mat-mini-fab, matTooltip="View Workflow", color="accent", (click)='view(element)')

+          //-   i.fa.fa-eye

+          //- button.mr-3(mat-mini-fab, matTooltip="Edit", color="primary", (click)='edit(element)')

+          //-   i.fa.fa-pencil

+          //- button.text-white(mat-mini-fab, matTooltip="Delete", color='warn', (click)='delete(element)')

+          //-   i.fa.fa-remove

+

+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")

+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")

+

+    //-mat-paginator([length]="resultsLength", [pageSizeOptions]="[10, 25, 100]", [hidden]="loading")

+

+    

+  

+  .row.mt-2

+    .col

+

+      //- Create

+      button.mr-2.pull-right(color="primary", matTooltip="Create Test Instance", mat-icon-button, (click)="createInstance()", [disabled] = "((!selectedSingleRow) || (selectedLockedRows))")

+        mat-icon add

+      //- Lock

+      button.mr-4.pull-right(color="primary", matTooltip="Lock Test Definition", mat-icon-button, (click)="lockMultiple()", [disabled]="(!hasSelectedRows)", [hidden]="(!selectedUnlockedRows)")

+        mat-icon lock

+      //- Unlock

+      button.mr-2.pull-right(color="primary", matTooltip="Unlock Test Definition", mat-icon-button, (click)="unlockMultiple()", [disabled]="", [hidden] = "((!selectedLockedRows) || (!selectedRows))")

+        mat-icon lock_open

+

+      //- Edit

+      button.mr-2.pull-right(color="primary", matTooltip="Edit Test Definition", mat-icon-button, (click)="edit()", [disabled]="(!selectedSingleRow)") 

+        mat-icon edit

+      //- Delete

+      button.mr-2.pull-right(color="primary", matTooltip="Delete Test Definition", mat-icon-button, (click)="deleteMultiple()", [disabled]="!hasSelectedRows") 

+        mat-icon delete_forever

+      //- Modeler

+      button.mr-2.pull-right(mat-raised-button, color="primary", (click)="testDefinitionModeler()", [disabled]="(!selectedSingleRow)") Modeler

+

+    //-div(style="width: 100%", [hidden]="!loading")  **Took this out because it would load quicker

+      mat-spinner(style="margin: auto", color="primary")

+

+    //- div(style="width: 100%;height: 40px;")

+

+  .row  

+    .col

+      ag-grid-angular.ag-theme-material(

+        style="width:100%; height: 600px",

+        [rowData]="rowData",

+        [columnDefs]="columns",

+        rowSelection="multiple",

+        [rowMultiSelectWithClick]="true",

+        (rowSelected)="onRowSelected($event)",

+        (gridReady)="onGridReady($event)",

+        [enableCellChangeFlash]="true",

+        (cellDoubleClicked)="navToDefinition($event)",

+        [singleClickEdit]="true"

+      )

+

+

+

+

+    //.card-body

+      .row

+        div.col-6

+          input.form-control.bg-light.mb-1([(ngModel)]="search.test_head_id", type="text", placeholder="Search...")

+        div.col-6

+          button.bg-primary.mbtn.pull-right.text-white.mb-1(mat-raised-button, (click)='createTestHead()') Create VTH

+      table.table.table-striped([mfData]='data', #mf='mfDataTable', [mfRowsOnPage]='5')

+        thead

+          tr

+            th(style='width: 20%')

+              mfDefaultSorter(by='name') Name

+            th(style='width: 50%')

+              mfDefaultSorter(by='creator') Creator

+            th(style='width: 10%')

+              mfDefaultSorter(by='date') Date 

+            th(style='width: 20%') Options   

+        tbody

+          tr

+            td Ping Test Head

+            td Tiffany, Patrick 

+            td 7/21/18

+            td 

+              button.bg-primary.mbtn.text-white.mr-1(mat-mini-fab, aria-label='View', (click)='viewTestHead(null)') 

+                i.fa.fa-eye

+              button.bg-primary.mbtn.text-white.mr-1(mat-mini-fab, aria-label='Edit', (click)='editTestHead()')

+                i.fa.fa-pencil

+              button.mbtn.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='deleteTestHead()')

+                i.fa.fa-remove

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
index 0000000..fa45135
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/tests.component.scss
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.mbtn:focus {

+	outline: none;

+}

+.mat-warn {

+    background-color: red;

+    color:red;

+}

+.bg-accent{

+    background-color: brown

+}

+.mat-mini-fab{

+    width: 30px !important;

+    height: 30px !important;

+    line-height: 10px !important;

+}

+.dropdown-toggle::after {

+    display:none;

+}
\ 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
index 0000000..9ca1b08
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/tests.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestsComponent } from './tests.component';

+

+describe('TestsComponent', () => {

+  let component: TestsComponent;

+  let fixture: ComponentFixture<TestsComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestsComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestsComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..6b0019e
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/tests.component.ts
@@ -0,0 +1,535 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewContainerRef, ViewChild, AfterContentInit, OnDestroy } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { routerTransition } from '../../router.animations';

+import { ListService } from '../../shared/services/list.service';

+import { Router } from '@angular/router';

+import { TestDefinitionService } from '../../shared/services/test-definition.service';

+import { TestInstanceService } from '../../shared/services/test-instance.service';

+import { MatTableDataSource } from '@angular/material/table';

+import { MatPaginator, MatDialog, MatSnackBar } from '@angular/material';

+import { AlertModalComponent } from '../../shared/modules/alert-modal/alert-modal.component';

+import { CreateTestComponent } from '../onboarding/create-test/create-test.component';

+import { TestDefinitionModalComponent } from 'app/shared/modules/test-definition-modal/test-definition-modal.component';

+import { ViewWorkflowModalComponent } from 'app/shared/modules/view-workflow-modal/view-workflow-modal.component';

+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';

+import { TestInstanceModalComponent } from '../../shared/modules/test-instance-modal/test-instance-modal.component';

+import { UserService } from 'app/shared/services/user.service';

+import { CookieService } from "ngx-cookie-service";

+import { GroupService } from 'app/shared/services/group.service';

+import { appInitializerFactory } from '@angular/platform-browser/src/browser/server-transition';

+import { element } from '@angular/core/src/render3/instructions';

+import { GridOptionsWrapper, RowNode, initialiseAgGridWithAngular1 } from 'ag-grid-community';

+import { every } from 'rxjs/operators';

+import { Subscription } from 'rxjs';

+

+@Component({

+  selector: 'app-tests',

+  templateUrl: './tests.component.pug',

+  styleUrls: ['./tests.component.scss'],

+  animations: [routerTransition()]

+})

+export class TestsComponent implements OnInit, OnDestroy {

+

+  private toDestroy: Array<Subscription> = [];

+

+  public dataSource;

+  public displayedColumns: string[] = ['lock', 'name', 'description', 'id', 'processDefinitionKey', 'options'];

+  public resultsLength;

+  public loading = false;

+

+

+  public columns = [

+    

+    {headerName: 'Name', field: 'testName', sortable: true, filter: true, resizable: true, checkboxSelection:true, headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true, width: 300},

+    {headerName: 'Description', field: 'testDescription', sortable: true, filter: true, resizable: true},

+    {headerName: 'Id', field: '_id', sortable: true, filter: true, resizable: true, editable: true},

+    {headerName: 'Process Definition key', field: 'processDefinitionKey', sortable: true, filter: true, resizable: true},

+    {headerName: '', field: 'disabled', cellRenderer: this.disabledIndicator, hide: false, width: 80}

+    

+  ];

+  public rowData;

+  

+  /*

+  public rowData =  [

+    { _id: '5cfe7e5d6f4e5d0040a3b235', testDescription: 'For testing', testName: "testflow", processDefinitionKey: "demo"},

+    { make: 'Ford', model: 'Mondeo', price: 32000 },

+    { make: 'Porsche', model: 'Boxter', price: 72000 }

+]; */

+

+  public hasSelectedRows = false;

+  public selectedSingleRow = false;

+  public selectedUnlockedRows = true;

+  public selectedLockedRows = false; 

+

+  private gridApi;

+  private gridColumnApi;

+  private selectedRows = {};

+

+  @ViewChild(MatPaginator) paginator: MatPaginator;

+

+  constructor(private http: HttpClient,

+    private router: Router,

+    private viewRef: ViewContainerRef,

+    private testDefinition: TestDefinitionService,

+    private modal: MatDialog,

+    private snack: MatSnackBar,

+    private user: UserService,

+    private testInstanceService: TestInstanceService,

+    private cookie: CookieService,

+    private _groups: GroupService

+  ) { }

+  

+  ngOnInit() {

+

+    this.setComponentData(this._groups.getGroup());

+    this.toDestroy.push(this._groups.groupChange().subscribe(group => {

+      this.setComponentData(group);

+    }));

+

+

+  }

+

+  ngOnDestroy() {

+    this.toDestroy.forEach(elem => elem.unsubscribe());

+  }

+

+  setComponentData(group) {

+    

+    if(!group){

+      return;

+    }

+

+    this.loading = true;

+

+    this.dataSource = new MatTableDataSource();

+    this.dataSource.paginator = this.paginator;

+

+

+  

+    this.testDefinition.find({

+      $limit: -1,

+      groupId: group['_id'],

+      $sort: {

+        createdAt: -1

+      },

+      $select: ['testName', 'testDescription', 'processDefinitionKey', 'bpmnInstances.isDeployed', 'disabled', 'groupId']

+    }).subscribe((list) => {

+      this.dataSource.data = list;

+      this.resultsLength = this.dataSource.data.length;

+      this.loading = false;

+      // Getting row data filled with list

+      this.rowData = list;

+

+

+

+      //console.log("This is the rowdata: "+ JSON.stringify(this.rowData[1]))

+      //this.rowData = [].concat.apply([], list);

+    })

+  

+   

+  }

+

+  applyFilter(filterValue: string) {

+    this.dataSource.filter = filterValue.trim().toLowerCase();

+  }

+//createInstance(element)

+  createInstance() {

+    

+

+    this.selectedRows = this.gridApi.getSelectedRows().map(({ _id, testName }) => ({_id, testName}));

+    

+    const create = this.modal.open(TestInstanceModalComponent, {

+      width: '90%',

+      data: {

+        td: this.selectedRows[0]._id//element._id

+      },

+      disableClose: true

+    });

+  }

+

+  create() {

+    let create = this.modal.open(TestDefinitionModalComponent, {

+      disableClose: true

+    });

+

+    create.afterClosed().subscribe(res => {

+      this.ngOnInit();

+    })

+  }

+

+

+  // view(td){

+  //   this.modal.open(ViewWorkflowModalComponent, {

+  //     width: '90%',

+  //     height: '70%',

+  //     maxWidth: '100%',

+  //     data: {

+  //       id: td._id

+  //     }

+  //   });

+  // }

+

+

+

+

+

+  deleteMultiple(){

+    for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){

+      this.delete(i);

+    }

+  }

+

+  delete(td) {

+

+    this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testName }) => ({_id, testName}));

+    const deleter = this.modal.open(AlertModalComponent, {

+      width: '250px',

+      data: {

+        type: 'confirmation',

+        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.'

+      }

+    });

+

+    deleter.afterClosed().subscribe(result => {

+      if (result) {

+        this.testDefinition.delete(this.selectedRows[td]._id).subscribe(response => {

+          this.snack.openFromComponent(AlertSnackbarComponent, {

+            duration: 1500,

+            data: {

+              message: 'Test definition was deleted'

+            }

+          })

+          //this.ngOnInit();

+          this.setComponentData(this._groups.getGroup());

+        });

+      }

+    });

+  }

+

+  edit() {

+    this.selectedRows = this.gridApi.getSelectedRows().map(({_id }) => ({_id}));

+    var editor = this.modal.open(TestDefinitionModalComponent, {

+      disableClose: true,

+      data: {

+        testDefinitionId: this.selectedRows[0]._id

+      }

+    });

+  }

+

+  lockMultiple(){

+   

+    for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){

+      this.lock(i);

+    }

+

+  }

+

+

+  lock(td) {

+    this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testName, groupId,  }) => ({_id, testName, groupId}));

+

+    let user = JSON.parse(this.cookie.get('currentUser'));

+    let isAdmin = false;

+    for (let i = 0; i < user.groups.length; i++) {

+      if (this.selectedRows[td].groupId === user.groups[i].groupId) {

+        if (user.groups[i].permissions.includes("admin")) {

+          isAdmin = true;

+        }

+      }

+    }

+    user = '';

+    if (!isAdmin) {

+      this.modal.open(AlertModalComponent, {

+        width: '250px',

+        data: {

+          type: 'alert',

+          message: 'You do not have the correct permissions to lock/unlock test definitions.'

+        }

+      })

+      return;

+    }

+    this.modal.open(AlertModalComponent, {

+      width: '250px',

+      data: {

+        type: 'confirmation',

+        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.'

+      }

+    }).afterClosed().subscribe((result) => {

+      if (result) {

+        let testDef = {

+          '_id': this.selectedRows[td]._id,

+          'disabled': true

+        }

+        this.testDefinition.patch(testDef).subscribe((res) => {

+          this.selectedRows[td].disabled = true;

+          this.testInstanceService.find({ $limit: -1, testDefinitionId: this.selectedRows[td]._id }).subscribe((result) => {

+            

+            

+

+            if (result['length']) {

+              for (let i = 0; i < result['length']; i++) {

+                let ti = {

+                  '_id': null,

+                  'disabled': true

+                }

+                ti._id = result[i]._id;

+                ti.disabled = true;

+                let temp = ti;

+              

+                this.testInstanceService.patch(ti).subscribe((results) => {

+                 

+                  this.snack.openFromComponent(AlertSnackbarComponent, {

+                    duration: 1500,

+                    data: {

+                      message: 'Test Instance ' + results['testInstanceName'] + ' was locked'

+                    }

+                  })

+                });

+              }

+            } else {

+              let ti = {

+                '_id': null,

+                'disabled': true

+              }

+              ti._id = result['_id'];

+              this.testInstanceService.patch(ti).subscribe((results) => {

+                this.snack.openFromComponent(AlertSnackbarComponent, {

+                  duration: 1500,

+                  data: {

+                    message: 'Test Instance ' + results['testInstanceName'] + ' was locked'

+                  }

+                })

+              });;

+            }

+          });

+          this.setComponentData(this._groups.getGroup());

+        }, (error) => {

+          this.modal.open(AlertModalComponent, {

+            width: '250px',

+            data: {

+              type: "alert",

+              message: 'Test Definition could not be locked.'

+            }

+          })

+        });

+

+      }

+    })

+  }

+

+

+  updateData(){

+    

+    this.setComponentData(this._groups.getGroup());

+  }

+

+  unlockMultiple() {

+    for(let i = 0; i < this.gridApi.getSelectedNodes().length; i++){

+      this.unlock(i);

+    }

+  }

+//unlock multiple and loop through single unlock

+  unlock(td) {

+    this.selectedRows = this.gridApi.getSelectedRows().map(({_id, testName, groupId,  }) => ({_id, testName, groupId}));

+    let user = JSON.parse(this.cookie.get('currentUser'));

+    let isAdmin = false;

+    for (let i = 0; i < user.groups.length; i++) {

+      if (this.selectedRows[td].groupId === user.groups[i].groupId) {

+        if (user.groups[i].permissions.includes("admin")) {

+          isAdmin = true;

+        }

+      }

+    }

+    user = '';

+    if (!isAdmin) {

+      this.modal.open(AlertModalComponent, {

+        width: '250px',

+        data: {

+          type: 'alert',

+          message: 'You do not have the correct permissions to lock/unlock test definitions.'

+        }

+      })

+      return;

+    }

+

+    this.modal.open(AlertModalComponent, {

+      width: '250px',

+      data: {

+        type: 'confirmation',

+        message: 'Are you sure you want to unlock ' + td.testName + '? All test instances using this test definition will be unlocked as well.'

+      }

+    }).afterClosed().subscribe((result) => {

+      if (result) {

+        let testDef = {

+          '_id': this.selectedRows[td]._id,

+          'disabled': false

+        }

+        this.testDefinition.patch(testDef).subscribe((res) => {

+          this.selectedRows[td].disabled = false;

+          this.testInstanceService.find({ $limit: -1, testDefinitionId: this.selectedRows[td]._id }).subscribe((result) => {

+           

+            // console.log(result);

+            if (result['length']) {

+              for (let i = 0; i < result['length']; i++) {

+                let ti = {

+                  '_id': null,

+                  'disabled': false

+                }

+                ti._id = result[i]._id;

+                ti.disabled = false;

+                this.testInstanceService.patch(ti).subscribe((results) => {

+                  this.snack.openFromComponent(AlertSnackbarComponent, {

+                    duration: 1500,

+                    data: {

+                      message: 'Test Instance ' + results['testInstanceName'] + ' was unlocked'

+                    }

+                  })

+                });

+              }

+            } else {

+              let ti = {

+                '_id': null,

+                'disabled': false

+              }

+              ti._id = result['_id'];

+              

+              this.testInstanceService.patch(ti).subscribe((results) => {

+                this.snack.openFromComponent(AlertSnackbarComponent, {

+                  duration: 1500,

+                  data: {

+                    message: 'Test Instance ' + results['testInstanceName'] + ' was unlocked'

+                  }

+                })

+              });;

+            }

+          });

+          this.setComponentData(this._groups.getGroup());

+        }, (error) => {

+          this.modal.open(AlertModalComponent, {

+            width: '250px',

+            data: {

+              type: "alert",

+              message: 'Test Definition could not be locked.'

+            }

+          })

+        });

+

+      }

+    })

+  }

+

+

+  isDeployed(element) {

+    let deployed = false;

+    if (element.bpmnInstances) {

+      element.bpmnInstances.forEach(elem => {

+        if (elem.isDeployed) {

+          deployed = true;

+        }

+      });

+    }

+    return deployed;

+  }

+

+

+

+  onRowSelected(event){

+

+    this.selectedRows = this.gridApi.getSelectedRows().map(({ _id, disabled  }) => ({ _id, disabled}));

+

+    if(event.api.getSelectedNodes().length > 0){

+      this.hasSelectedRows = true;

+

+      //Checks for all Unlocked rows

+      for (let i = 0; i < event.api.getSelectedNodes().length; i++ )

+      {

+

+        if(!this.selectedRows[i].disabled)

+        {

+          this.selectedUnlockedRows = true;

+        }

+        else{

+          this.selectedUnlockedRows = false;

+          break;

+        }

+      }

+

+      //Checks for all Locked rows

+      for (let i = 0; i < event.api.getSelectedNodes().length; i++ )

+      {

+

+        if(this.selectedRows[i].disabled)

+        {

+          this.selectedLockedRows = true;

+        }

+        else{

+          this.selectedLockedRows = false;

+          break;

+        }

+      }

+

+

+

+

+

+    }

+    else{

+      this.hasSelectedRows = false;

+      this.selectedLockedRows = false;

+      this.selectedUnlockedRows = true; 

+

+    }

+    //One Row was selected

+    if((event.api.getSelectedNodes().length == 1)){

+      this.selectedSingleRow = true;

+     

+    }else{

+      this.selectedSingleRow = false;

+    }

+

+  }

+

+  onGridReady(params){

+    this.gridApi = params.api;

+    

+    this.gridColumnApi = params.columnApi;

+

+    //auto size the column widths

+    this.gridColumnApi.autoSizeColumns(['name']);

+  }

+

+  disabledIndicator(params){

+    if (params.value){

+      return `<mat-icon class="mat-icon mat-icon-no-color" role="img" >

+       locked</mat-icon>`;   

+    }

+  }

+

+

+

+

+  navToDefinition(event){

+    this.router.navigate(['/test-definitions', event.data._id]);

+  }

+

+  testDefinitionModeler(){

+    this.router.navigate(['/modeler'], {queryParams: {testDefinitionId: this.selectedRows[0]._id}});

+  }

+

+}

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
index 0000000..baebc53
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/tests.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestsModule } from './tests.module';

+

+describe('TestsModule', () => {

+  let testsModule: TestsModule;

+

+  beforeEach(() => {

+    testsModule = new TestsModule();

+  });

+

+  it('should create an instance', () => {

+    expect(testsModule).toBeTruthy();

+  });

+});

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
index 0000000..b1f5aa4
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/tests/tests.module.ts
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {CommonModule} from '@angular/common';

+import {TestsRoutingModule} from './tests-routing.module';

+import {TestsComponent} from './tests.component';

+import {

+    MAT_DIALOG_DATA,

+    MatButtonModule,

+    MatFormFieldModule,

+    MatInputModule,

+    MatPaginatorModule,

+    MatSnackBarModule,

+    MatTableModule,

+    MatTooltipModule,

+    MatProgressSpinnerModule,

+    MatIconModule,

+    MatDatepickerModule

+} from '@angular/material';

+import {PageHeaderModule} from '../../shared';

+import {FilterPipeModule} from 'ngx-filter-pipe';

+import {FormsModule} from '@angular/forms';

+import {TestHeadModalModule} from '../../shared/modules/test-head-modal/test-head-modal.module';

+import {AlertModalModule} from '../../shared/modules/alert-modal/alert-modal.module';

+import {TestDefinitionModalModule} from 'app/shared/modules/test-definition-modal/test-definition-modal.module';

+import {CreateTestModule} from '../onboarding/create-test/create-test.module';

+import {ViewWorkflowModalModule} from 'app/shared/modules/view-workflow-modal/view-workflow-modal.module';

+import { CreateTestInstanceFormModule } from '../../shared/modules/create-test-instance-form/create-test-instance-form.module';

+import { TestInstanceModalModule } from '../../shared/modules/test-instance-modal/test-instance-modal.module';

+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

+import { AgGridModule } from 'ag-grid-angular';

+import { TestDefinitionDetailsComponent } from './test-definition-details/test-definition-details.component';

+import {MatCardModule} from '@angular/material/card';

+import { DashboardModule } from '../dashboard/dashboard.module';

+

+@NgModule({

+    imports: [

+        CommonModule,

+        TestsRoutingModule,

+        PageHeaderModule,

+        FormsModule,

+        FilterPipeModule,

+        MatButtonModule,

+        MatTableModule,

+        MatFormFieldModule,

+        MatInputModule,

+        MatPaginatorModule,

+        TestHeadModalModule,

+        TestInstanceModalModule,

+        AlertModalModule,

+        TestDefinitionModalModule,

+        CreateTestModule,

+        TestDefinitionModalModule,

+        MatTooltipModule,

+        ViewWorkflowModalModule,

+        MatSnackBarModule,

+        MatProgressSpinnerModule,

+        NgbModule,

+        AgGridModule.withComponents([]),

+        MatIconModule,

+        MatCardModule,

+        DashboardModule,

+        MatDatepickerModule

+    ],

+    declarations: [TestsComponent, TestDefinitionDetailsComponent],

+    providers: [{provide: MAT_DIALOG_DATA, useValue: {}}]

+})

+export class TestsModule {

+}

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
index 0000000..64eaf38
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/user-management/user-management-routing.module.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { UserManagementComponent } from './user-management.component';

+

+

+const routes: Routes = [{

+    path:'', component: UserManagementComponent}];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class UserManagementRoutingModule { }

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
index 0000000..5e75101
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/user-management/user-management.component.pug
@@ -0,0 +1,86 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+  app-page-header([heading]="'User Management'", [icon]="'fa-edit'")

+

+  .card-mb-12

+    .pull-left

+      mat-form-field

+        input(matInput, name="filter", value="{{filterString}}", (keyup)="applyFilter($event.target.value)", placeholder="Filter")

+

+    div(style="width: 100%", [hidden]="!loading")

+      mat-spinner(style="margin: auto", color="primary")

+

+    table.mat-elevation-z8(mat-table, *ngIf="dataSource.data && dataSource.data.length > 0", [dataSource]="dataSource", style="width: 100%", [hidden]="loading")

+

+      ng-container(matColumnDef="lastName")

+        th(mat-header-cell, *matHeaderCellDef) lastName

+        td(mat-cell, *matCellDef="let element") {{ element.lastName }}

+

+      ng-container(matColumnDef="firstName")

+        th(mat-header-cell, *matHeaderCellDef) First Name

+        td(mat-cell, *matCellDef="let element") {{ element.firstName }}

+

+      ng-container(matColumnDef="email")

+        th(mat-header-cell, *matHeaderCellDef) Email

+        td(mat-cell, *matCellDef="let element") {{ element.email}}

+      

+      ng-container(matColumnDef="addGroups")

+        th(mat-header-cell, *matHeaderCellDef) Add to Group

+        td(mat-cell, *matCellDef="let element")

+          .dropdown(ngbDropdown, autoClose="outside", (openChange)="dropdownChange()", placement="left-top")

+            button(mat-mini-fab, color="primary", ngbDropdownToggle, (click)="null")

+              i.fa.fa-caret-down

+            .dropdown-menu(ngbDropdownMenu)

+              h4.mb-2.ml-1(style="font-weight: bold;") Change Groups

+              input.ml-1(matInput, type='search', placeholder='Search...', color='blue', [(ngModel)]='search.groupName')

+              div(style="max-height: 300px; overflow-y: scroll")

+                .px-4.py-3

+                  .mr-2.ml-2(*ngFor="let group of groups | filterBy:search")

+                    mat-checkbox((change)="addRemoveGroupList(element, group._id, $event)", [(ngModel)]="element[group._id]") {{group.groupName}} 

+              div(style="text-align: center")            

+                button.primary.mr-1(mat-raised-button, [disabled]= "!element.groupsToAddRemove || element.groupsToAddRemove.length <= 0", aria-label='Edit', color="primary", (click)='addGroups(element)') Add

+                button(mat-raised-button, [disabled]= "!element.groupsToAddRemove || element.groupsToAddRemove.length <= 0", color="warn", (click)='removeGroups(element)')  Remove

+

+              //- a.dropdown-item(*ngFor="let group of groups", (click)='addGroupsList(element, group._id)')

+              //-   span.pl-1 {{group.groupName}}

+              //-   i.fa.fa-check(*ngIf='element.groupsToAdd !== undefined && element.groupsToAdd.includes(group._id)')

+                

+              

+                

+            //- mat-select.mr-1((selectionChange)="onChange(element)", style="width: 30%; background: #80808066",[(ngModel)]="element['groupToAddRemove']")

+            //-   mat-option(*ngFor="let group of groups", value="{{group._id}}") {{ group.groupName }}

+            //- button.mr-1(mat-mini-fab, aria-label='Edit', color="primary", (click)='addGroup(element)')

+            //-   i.fa.fa-plus

+            //- button.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='removeGroup(element)')

+            //-   i.fa.fa-remove

+          

+      ng-container(matColumnDef="isVerified")

+        th(mat-header-cell, *matHeaderCellDef) Verified

+        td(mat-cell, *matCellDef="let element") {{element.isVerified ? "Yes" : "No"}}

+

+      ng-container(matColumnDef="enabled")

+        th(mat-header-cell, *matHeaderCellDef) Enabled

+        td(mat-cell, *matCellDef="let element")

+            mat-slide-toggle([(ngModel)]="element.enabled", "color"="primary", (input)="enableUser($event, element)")

+      //mat-slide-toggle([checked]="element.enabled? true : false", "color"="primary", (input)="enableUser($event, element)")

+

+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")

+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")

+

+    mat-paginator([length]="resultsLength", [pageSizeOptions]="[10, 25, 100]", [hidden]="loading")

+

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
index 0000000..e177191
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/user-management/user-management.component.scss
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.mat-mini-fab{

+    width: 30px !important;

+    height: 30px !important;

+    line-height: 10px !important;

+}

+

+.dropdown-toggle::after {

+    display:none;

+}
\ 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
index 0000000..8dea47b
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/user-management/user-management.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { UserManagementComponent } from './user-management.component';

+

+describe('UserManagementComponent', () => {

+  let component: UserManagementComponent;

+  let fixture: ComponentFixture<UserManagementComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ UserManagementComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(UserManagementComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..4ce454f
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/user-management/user-management.component.ts
@@ -0,0 +1,347 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {Component, OnInit, ViewContainerRef, ViewChild} from '@angular/core';

+import {ActivatedRoute, Router} from '@angular/router';

+import { MatPaginator, MatDialog, MatSnackBar } from '@angular/material';

+import { MatTableDataSource } from '@angular/material/table';

+import {HttpClient} from "@angular/common/http";

+import {UserService} from "../../shared/services/user.service";

+import { routerTransition } from '../../router.animations';

+import { ListService } from '../../shared/services/list.service';

+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';

+import { GroupService } from 'app/shared/services/group.service';

+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';

+import * as organizeGroups from '../../../../../server/src/feathers/hooks/permissions/get-permissions';

+import { CookieService } from 'ngx-cookie-service';

+

+@Component({

+  selector: 'app-user-management',

+  templateUrl: './user-management.component.pug',

+  styleUrls: ['./user-management.component.scss'],

+  animations: [routerTransition()]

+

+})

+export class UserManagementComponent implements OnInit {

+

+    public dataSource;

+    public displayedColumns: string[] = ['lastName', 'firstName', 'email', 'addGroups', 'isVerified', 'enabled'];

+    public resultsLength;

+    public loading = false;

+    public filterString = "";

+    public groups;

+    public search;

+    public currentUser;

+    

+    @ViewChild(MatPaginator) paginator: MatPaginator;

+

+

+    constructor(private http: HttpClient,

+        private router: Router,

+        private viewRef: ViewContainerRef,

+        private list: ListService,

+        private modal: MatDialog,

+        private snack: MatSnackBar,

+        private user: UserService,

+        private route: ActivatedRoute,

+        private groupService: GroupService,

+        private cookie: CookieService

+    ) { }

+

+    ngOnInit() {

+        this.loading = true;

+        this.groups = [];

+        this.search = {};

+        this.search.groupName = '';

+        this.currentUser = JSON.parse(this.cookie.get('currentUser'));

+        this.groupService.find({$limit: -1}).subscribe((result) => {

+            if(result){

+                this.groups = organizeGroups(this.currentUser, result);

+                

+            }

+        })

+        this.route.queryParamMap.subscribe(queryParams => {

+            this.filterString = queryParams.get("filter");

+            if(!this.filterString){

+                this.filterString = "";

+            }

+        });

+        this.list.createList('td');

+        //["$limit=-1", "$sort[createdAt]=-1", "$select[]=lastName", "$select[]=firstName", "$select[]=email", "$select[]=isVerified", "$select[]=enabled"]

+        this.user.find({

+            $limit: -1,

+            $sort: {

+                createdAt: -1,

+            },

+            $select: ['lastName', 'firstName', 'email', 'isVerified', 'enabled', 'groups']

+        }).subscribe((list) => {

+            this.list.changeMessage('td', list);

+            this.loading = false;

+

+        });

+

+        this.dataSource = new MatTableDataSource();

+        this.dataSource.paginator = this.paginator;

+

+        this.list.listMap['td'].currentList.subscribe((list) =>{

+            if(list){

+                this.dataSource.data = list;

+                this.resultsLength = this.dataSource.data.length;

+                this.applyFilter(this.filterString)

+            }

+        });

+

+    }

+

+    applyFilter(filterValue: string) {

+        this.dataSource.filter = filterValue.trim().toLowerCase();

+    }

+

+    applyGroupFilter(filterValue: string){

+        this.groups.filter = filterValue.trim().toLowerCase();

+    }

+

+    dropdownChange(){

+        this.search.groupName = '';

+        for(let i in this.groups){

+            this.groups[i].selected = false;

+        }

+        

+    }

+

+    

+    addRemoveGroupList(element, groupId, event){

+        if(event.checked){

+            if (element.groupsToAddRemove){

+                element.groupsToAddRemove.push(groupId);

+            }else{

+                element.groupsToAddRemove = [];

+                element.groupsToAddRemove.push(groupId);

+            }

+        }else{

+            if(element.groupsToAddRemove){

+                let temp = element.groupsToAddRemove.indexOf(groupId)

+                if(temp >= 0)

+                    element.groupsToAddRemove.splice(temp, 1);

+            }

+        }

+        

+    }

+

+    removeGroups(user){

+        this.modal.open(AlertModalComponent, {

+            width: "250px",

+            data: {

+                type: "confirmation",

+                message: "Are you sure you want to remove " + user.firstName + " " + user.lastName + " from groups?"

+            }

+        }).afterClosed().subscribe((results) => {

+            if(results === undefined){

+                return;

+            }

+            if(results){

+                for(let i in user.groupsToAddRemove){

+                    user[user.groupsToAddRemove[i]] = false;

+                    let index = this.groups.findIndex(function(group){ return group._id == user.groupsToAddRemove[i]; })

+                    if(index >= 0 && this.groups[index].members){

+                        let memberIndex = this.groups[index].members.findIndex(function(member){return member.userId.toString() == user._id.toString()})

+                        if(memberIndex >= 0){

+                            this.groups[index].members.splice(memberIndex, 1);

+                            let groupPatch = {

+                                _id : this.groups[index]._id,

+                                members: this.groups[index].members

+                            }

+                            this.groupService.patch(groupPatch).subscribe((res) => {

+                                let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' removed from group!';

+                                this.snack.openFromComponent(AlertSnackbarComponent, {

+                                    duration: 1500,

+                                    data: {

+                                        message: snackMessage

+                                    }

+                                });

+                            });   

+                        }

+                    }

+                  

+                }

+            }else{

+                return;

+            }

+            // let userPatch = {

+            //     _id : user._id,

+            //     groups: user.groups

+            // };

+

+            // this.user.patch(userPatch).subscribe((res) => {

+            //     let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' removed from group!';

+            //     this.snack.openFromComponent(AlertSnackbarComponent, {

+            //         duration: 1500,

+            //         data: {

+            //             message: snackMessage

+            //         }

+            //     })

+            // });

+            user.groupsToAddRemove = [];

+            

+        });

+    }

+    //add "Change Groups" header to management dropdown\

+    addGroups(user){

+        this.modal.open(AlertModalComponent, {

+            width: "250px",

+            data: {

+                type: "userAdmin",

+                message: "Would you like to add as group user or group admin?"

+            }

+        }).afterClosed().subscribe((results) => {

+            if(results === undefined){

+                return;

+            }

+            if(results){

+                for(let i in user.groupsToAddRemove){

+                    user[user.groupsToAddRemove[i]] = false;

+                    let groupPatch = {

+                        _id : user.groupsToAddRemove[i],

+                        $push: { members: { userId : user._id, roles: ["admin"]}}

+                    }

+                    

+

+                    let index = this.groups.findIndex(function(group){ return group._id == user.groupsToAddRemove[i]; })

+                    if(index >= 0 && this.groups[index].members){

+                        let memberIndex = this.groups[index].members.findIndex(function(member){return member.userId.toString() == user._id.toString()});

+                        

+                        if(memberIndex >= 0 && !this.groups[index].members[memberIndex]["roles"].includes("admin")){

+                            groupPatch = this.groups[index];

+                            groupPatch["members"][memberIndex].roles.push("admin");

+                        }else if (memberIndex < 0) {

+                            groupPatch = {

+                                _id : user.groupsToAddRemove[i],

+                                $push: { members: { userId : user._id, roles: ["admin"]}}

+                            }

+                        }else{

+                            let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' already group admin!';

+                            this.snack.openFromComponent(AlertSnackbarComponent, {

+                                duration: 1500,

+                                data: {

+                                    message: snackMessage

+                                }

+                            });

+                            continue;

+                        }

+                    }

+                    this.groupService.patch(groupPatch).subscribe((res) => {

+                        let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' added to group!';

+                        this.snack.openFromComponent(AlertSnackbarComponent, {

+                            duration: 1500,

+                            data: {

+                                message: snackMessage

+                            }

+                        });

+                    });   

+                   

+                }

+            }else{

+                for(let i in user.groupsToAddRemove){

+                    user[user.groupsToAddRemove[i]] = false;

+                    let groupPatch = {

+                        _id : user.groupsToAddRemove[i],

+                        $push: { members: { userId : user._id, roles: [""]}}

+                    }

+                    

+

+                    let index = this.groups.findIndex(function(group){ return group._id == user.groupsToAddRemove[i]; })

+                    if(index >= 0 && this.groups[index].members){

+                        let memberIndex = this.groups[index].members.findIndex(function(member){return member.userId == user.groupsToAddRemove[i]})

+                        if(memberIndex >= 0 ){

+                            if( this.groups[index].members[memberIndex].roles.includes("admin")){

+                                groupPatch = this.groups[index];

+                                let adminIndex = groupPatch["members"][memberIndex].roles.findIndex(function(perm){return perm.toLowerCase() == "admin";});

+                                groupPatch["members"][memberIndex].roles.splice(adminIndex, 1);

+                            }else{

+                                return;

+                            }

+                        }else if (memberIndex < 0) {

+                            groupPatch = {

+                                _id : user.groupsToAddRemove[i],

+                                $push: { members: { userId : user._id, roles: [""]}}

+                            }

+                        }

+                    }

+                    this.groupService.patch(groupPatch).subscribe((res) => {

+                        let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' added to group!';

+                        this.snack.openFromComponent(AlertSnackbarComponent, {

+                            duration: 1500,

+                            data: {

+                                message: snackMessage

+                            }

+                        });

+                    });  

+                }

+            }

+            // let userPatch = {

+            //     _id : user._id,

+            //     groups: user.groups

+            // };

+

+            // this.user.patch(userPatch).subscribe((res) => {

+            //     let snackMessage = 'Success. ' + user.firstName + ' ' + user.lastName + ' added to group!';

+            //     this.snack.openFromComponent(AlertSnackbarComponent, {

+            //         duration: 1500,

+            //         data: {

+            //             message: snackMessage

+            //         }

+            //     })

+            // });

+            user.groupsToAddRemove = [];

+           

+        });

+

+        

+    }

+

+    enableUser(event, element){

+        console.log(element)

+        let oldVal = element.enabled;

+        if(event.target.checked === element.enabled){

+            //console.log("same");

+            return

+        }

+        this.user.enableUser(element._id, event.target.checked).subscribe(

+            (result) => {

+                element.enabled = result['enabled'];

+                let snackMessage = 'Success. Set enabled to : ' + result['enabled'];

+                this.snack.openFromComponent(AlertSnackbarComponent, {

+                    duration: 1500,

+                    data: {

+                        message: snackMessage

+                    }

+                })

+

+            },

+            (error) => {

+                element.enabled = oldVal;

+                let snackMessage = 'Could not set enabled to : ' + !oldVal;

+                this.snack.open(snackMessage, "Error", { duration: 1500 })

+

+

+            }

+

+        )

+

+

+    }

+

+}

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
index 0000000..529ddc8
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/user-management/user-management.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { UserManagementModule } from './user-management.module';

+

+describe('UserManagementModule', () => {

+  let userManagementModule: UserManagementModule;

+

+  beforeEach(() => {

+    userManagementModule = new UserManagementModule();

+  });

+

+  it('should create an instance', () => {

+    expect(userManagementModule).toBeTruthy();

+  });

+});

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
index 0000000..01a84b6
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/user-management/user-management.module.ts
@@ -0,0 +1,71 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {CommonModule} from '@angular/common';

+import {UserManagementRoutingModule} from './user-management-routing.module';

+import {UserManagementComponent} from './user-management.component';

+import {

+    MAT_DIALOG_DATA,

+    MatButtonModule,

+    MatFormFieldModule,

+    MatInputModule,

+    MatPaginatorModule,

+    MatSnackBarModule,

+    MatSelectModule,

+    MatTableModule,

+    MatTooltipModule,

+    MatProgressSpinnerModule,

+    MatSlideToggleModule,

+    MatOptionModule,

+    MatCheckboxModule,

+    MatIconModule

+} from '@angular/material';

+import {PageHeaderModule} from '../../shared';

+import {FilterPipeModule} from 'ngx-filter-pipe';

+import {FormsModule} from '@angular/forms';

+import {AlertModalModule} from '../../shared/modules/alert-modal/alert-modal.module';

+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

+import {AlertSnackbarModule} from "../../shared/modules/alert-snackbar/alert-snackbar.module";

+

+@NgModule({

+    imports: [

+        CommonModule,

+        PageHeaderModule,

+        FormsModule,

+        FilterPipeModule,

+        MatButtonModule,

+        MatCheckboxModule,

+        MatTableModule,

+        MatFormFieldModule,

+        MatInputModule,

+        MatIconModule,

+        MatSlideToggleModule,

+        MatPaginatorModule,

+        AlertModalModule,

+        MatTooltipModule,

+        MatSnackBarModule,

+        MatSelectModule,

+        MatOptionModule,

+        MatProgressSpinnerModule,

+        NgbModule,

+        AlertSnackbarModule,

+        UserManagementRoutingModule

+    ],

+    declarations: [UserManagementComponent],

+    providers: [{provide: MAT_DIALOG_DATA, useValue: {}}]

+})

+export class UserManagementModule { }

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
index 0000000..cd6f182
--- /dev/null
+++ 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
@@ -0,0 +1,17 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div(#linechartdiv, [style.height]="height")

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
index 0000000..1571dc1
--- /dev/null
+++ 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
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..af1de53
--- /dev/null
+++ 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
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestHeadExecutionsLineChartComponent } from './test-head-executions-line-chart.component';

+

+describe('TestHeadExecutionsLineChartComponent', () => {

+  let component: TestHeadExecutionsLineChartComponent;

+  let fixture: ComponentFixture<TestHeadExecutionsLineChartComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestHeadExecutionsLineChartComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestHeadExecutionsLineChartComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..2d4abf6
--- /dev/null
+++ 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
@@ -0,0 +1,190 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core';

+import { Subscription } from 'rxjs';

+import { StatsService } from 'app/layout/components/stats/stats.service';

+import * as moment from 'moment';

+import * as am4core from "@amcharts/amcharts4/core";

+import * as am4charts from "@amcharts/amcharts4/charts";

+

+@Component({

+  selector: 'app-test-head-executions-line-chart',

+  templateUrl: './test-head-executions-line-chart.component.pug',

+  styleUrls: ['./test-head-executions-line-chart.component.scss']

+})

+export class TestHeadExecutionsLineChartComponent implements OnInit {

+

+  private toDestroy: Array<Subscription> = [];

+

+  @ViewChild('linechartdiv') LineChartDiv: ElementRef;

+  @Input() height: string;

+  @Input() testHeadId;

+  @Output() total: EventEmitter<any> = new EventEmitter();

+

+  //public testDefinitionName = "Hello";

+  private chart: am4charts.XYChart;

+  private loadingIndicator;

+

+  constructor(private stats: StatsService) {

+  }

+

+  ngOnInit() {

+    

+

+    this.renderChart();

+

+    this.toDestroy.push(this.stats.onTDExecutionChangeStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onDefaultDataCallStarted().subscribe(res => {

+      this.showLoadingIndicator();

+    }));

+

+    this.toDestroy.push(this.stats.onDefaultDataCallFinished().subscribe(res => {

+      this.setChartData();

+    }));

+

+    this.toDestroy.push(this.stats.onTDExecutionChangeFinished().subscribe(res => {

+      this.setChartData();

+    }));

+

+  }

+

+  ngOnDestroy(){

+    //destory chart

+    this.chart.dispose();

+  }

+

+  //Sets count to 0 for any dates that dont have data

+  setupPoints(rawData) {

+    let formattedData = []; 

+    let dayRange = moment(this.stats.filters.endDate).add(1, 'days').diff(moment(this.stats.filters.startDate), 'days');

+

+    for(let i = 0; i < dayRange; i++){

+      //find date in raw data

+      let d = rawData.find(e => moment(e.date).isSame(moment(this.stats.filters.startDate).add(i, 'days'), 'day'));

+      let count = 0;

+      if(d){

+        count = d.count;

+      }

+      formattedData.push({

+        date: moment(this.stats.filters.startDate).startOf('day').add(i, 'days').toDate(),

+        count: count

+      })

+    }

+

+    return formattedData;

+  }

+

+  showLoadingIndicator() {

+

+    //this.height = "380px";

+    if(!this.loadingIndicator){

+      this.loadingIndicator = this.chart.tooltipContainer.createChild(am4core.Container);

+      this.loadingIndicator.background.fill = am4core.color("#fff");

+      this.loadingIndicator.background.fillOpacity = 0.8;

+      this.loadingIndicator.width = am4core.percent(100);

+      this.loadingIndicator.height = am4core.percent(100);

+

+      let indicatorLabel = this.loadingIndicator.createChild(am4core.Label);

+      indicatorLabel.text = "Loading..";

+      indicatorLabel.align = "center";

+      indicatorLabel.valign = "middle";

+      indicatorLabel.fontSize = 18;

+      indicatorLabel.fontWeight = "bold";

+      indicatorLabel.dy = 50;

+

+      let loadingImage = this.loadingIndicator.createChild(am4core.Image);

+      //loadingImage.href = "https://img.devrant.com/devrant/rant/r_647810_4FeCH.gif";

+      loadingImage.href = "/assets/images/equalizer.gif";

+      //loadingImage.dataSource = "/loading-pies.svg"

+      loadingImage.align = "center";

+      loadingImage.valign = "middle";

+      loadingImage.horizontalCenter = "middle";

+      loadingImage.verticalCenter = "middle";

+      loadingImage.scale = 3.0;

+    }else{

+      this.loadingIndicator.show();

+    }

+  }

+

+  hideLoadingIndicator() {

+    this.loadingIndicator.hide();

+  }

+

+  setChartData() {

+    let data = [];

+    let total = 0;

+    this.stats.executionList.forEach(e => {

+      if (e.testHeadResults && e.testHeadResults.length > 0) {

+

+        e.testHeadResults.forEach((result, index) => {

+

+          if(result.testHeadId == this.testHeadId){

+            total++;

+            let dataIndex = data.findIndex(d => moment(d.date).isSame(result.startTime, 'day'));

+

+            if (dataIndex == -1) {

+              data.push({ date: moment(result.startTime).toDate(), count: 1 });

+            }else{

+              data[dataIndex].count++;

+            }

+            

+          }

+

+        })

+      }

+    })

+    

+    this.chart.data = this.setupPoints(data);

+    this.total.emit(total);

+    this.hideLoadingIndicator();

+  }

+

+  renderChart() {

+

+    if(this.chart){

+      this.chart.dispose();

+    }

+    this.chart = am4core.create(this.LineChartDiv.nativeElement, am4charts.XYChart);

+    this.chart.preloader.disabled = true;

+    this.showLoadingIndicator();

+

+    let dateAxis = this.chart.xAxes.push(new am4charts.DateAxis());

+    dateAxis.fontSize = "10px";

+

+    let valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());

+    valueAxis.title.text = "Executions";

+    valueAxis.title.fontSize = "10px";

+

+    let series = this.chart.series.push(new am4charts.LineSeries());

+    series.dataFields.dateX = "date";

+    series.dataFields.valueY = "count";

+    series.strokeWidth = 3;

+

+    series.fillOpacity = .5;

+    // series.tensionX = 0.8;

+    series.sequencedInterpolation = false;

+    series.tooltipText = "{valueY.value}";

+

+    this.chart.cursor = new am4charts.XYCursor();

+

+    this.chart.responsive.enabled = true;

+  }

+

+}

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
index 0000000..fce6e86
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.pug
@@ -0,0 +1,98 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+mat-card.mb-3

+    mat-card-header 

+        mat-card-title 

+            h4(*ngIf="testHead?.testHeadName") {{ testHead.testHeadName }} 

+        mat-card-subtitle(style="margin-bottom: 0px")

+            div(*ngIf="testHead?.testHeadDescription") {{testHead.testHeadDescription }}

+    mat-card-content

+        .row(*ngIf="testHead")

+            .col-sm

+                mat-form-field(*ngIf="testHead?.hostname")

+                    input(matInput, placeholder="Host Name", type="text", [value]="testHead.hostname", disabled, name="host")

+            .col-sm

+                mat-form-field(style="width:50px", *ngIf="testHead?.port") 

+                    input(matInput, placeholder="Port", type="text", [value]="testHead.port", disabled, name="port")

+            .col-sm

+                mat-form-field(*ngIf="testHead?.resourcePath") 

+                    input(matInput, placeholder="Resource Path", type="text", [value]="testHead.resourcePath", disabled, name="path")

+            .col-sm

+                mat-form-field(*ngIf="testHead?.groupId")

+                    input(matInput, placeholder="Group", type="text", [value]="testHead.groupId", disabled, name="group")

+            .col-sm

+                mat-form-field(style="width:50px",*ngIf="testHead?.isPublic != undefined")

+                    input(matInput, placeholder="Is Public", type="text", [value]="testHead.isPublic", disabled, name="public")

+

+div(style="position: relative")

+  .row

+    .col-12

+      .pull-left

+        mat-form-field(style="width:110px")

+          input(matInput, [matDatepicker]="fromPicker", placeholder="From Date", [(ngModel)]="stats.filters.startDate")

+          mat-datepicker-toggle(matSuffix, [for]="fromPicker")

+          mat-datepicker(#fromPicker)

+        mat-form-field.ml-2(style="width:110px")

+          input(matInput, [matDatepicker]="toPicker", placeholder="To Date", [(ngModel)]="stats.filters.endDate")

+          mat-datepicker-toggle(matSuffix, [for]="toPicker")

+          mat-datepicker(#toPicker)

+        button.ml-2(mat-icon-button, (click)="getData()") 

+          mat-icon arrow_forward

+          

+      .pull-right

+        mat-form-field

+          input(matInput, [ngModel]="totalExecutions", placeholder="Total Executions", disabled)

+

+  .row

+    .col-12

+      mat-card

+        mat-card-content

+          app-test-head-executions-line-chart(*ngIf="testHead", height="201px", [testHeadId]="testHead._id", (total)="setTotalExecutions($event)")

+

+//-   .row.mt-2

+//-     .col-lg-5

+//-       mat-card

+//-         mat-card-header

+//-           mat-card-title 

+//-             h5 Test Results

+//-         mat-card-content

+//-           app-pie-chart(height="230px")

+    

+//-     .col-lg-7

+//-       mat-card

+//-         mat-card-header

+//-           mat-card-title 

+//-             h5 Test Definition Usage

+//-         mat-card-content

+//-           app-test-definition-executions-bar-chart(height="230px")

+//-   .row.mt-2

+    

+//-     .col-lg-8

+//-       mat-card

+//-         mat-card-header

+//-           mat-card-title 

+//-             h5 Virtual Test Head Executions

+//-         mat-card-content

+//-           app-test-head-executions-line-chart(height="230px")

+    

+//-     .col-lg-4

+//-       mat-card

+//-         mat-card-header

+//-           mat-card-title 

+//-             h5 Virtual Test Head Usage

+//-         mat-card-content

+//-           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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..30b35bf
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { VirtualTestHeadDetailsComponent } from './virtual-test-head-details.component';

+

+describe('VirtualTestHeadDetailsComponent', () => {

+  let component: VirtualTestHeadDetailsComponent;

+  let fixture: ComponentFixture<VirtualTestHeadDetailsComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ VirtualTestHeadDetailsComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(VirtualTestHeadDetailsComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..4c1fe5c
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-head-details/virtual-test-head-details.component.ts
@@ -0,0 +1,104 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import {ActivatedRoute} from "@angular/router";

+import {TestHead} from "app/shared/models/test-head.model";

+import {TestHeadService} from "app/shared/services/test-head.service";

+import { Subscription } from 'rxjs';

+import { StatsService } from 'app/layout/components/stats/stats.service';

+

+

+@Component({

+  selector: 'app-virtual-test-head-details',

+  templateUrl: './virtual-test-head-details.component.pug',

+  styleUrls: ['./virtual-test-head-details.component.scss']

+})

+export class VirtualTestHeadDetailsComponent implements OnInit {

+

+  private toDestroy : Array<Subscription> = [];

+  testHead : TestHead;

+  public totalExecutions;

+  constructor(

+    private route: ActivatedRoute, 

+    private testHeadService : TestHeadService,

+    public stats: StatsService

+  ) { }

+

+  ngOnInit() {

+    this.toDestroy.push(this.route.params.subscribe(param => {

+      if(param.id){

+        this.toDestroy.push(this.testHeadService.get(param.id).subscribe(res => {

+          this.testHead = res as TestHead;

+          

+        }, err=>{

+          console.log(err);

+        }));

+

+        this.getData(param.id);

+      }

+    }));

+    

+  }

+

+  ngOnDestroy(){

+    this.toDestroy.forEach(e => {

+      e.unsubscribe()

+    });

+  }

+

+  getData(testHeadId?){

+    if(!testHeadId){

+      testHeadId = this.testHead._id

+    }

+

+    if(!testHeadId){

+      return;

+    }

+

+    this.stats.getDefaultData(1, {

+      'testHeadResults.testHeadId': testHeadId,

+      $select: [

+        'startTime',

+        'endTime',

+        "historicTestDefinition._id",

+        "historicTestDefinition.testName",

+        "historicTestInstance._id",

+        "historicTestInstance.testInstanceName",

+        "testHeadResults.startTime",

+        "testHeadResults.endTime",

+        "testHeadResults.testHeadName",

+        "testHeadResults.testHeadId",

+        "testHeadResults.testHeadGroupId",

+        "testHeadResults.statusCode",

+        'testResult'

+      ],

+      $limit: -1,

+      $sort: {

+        startTime: 1

+      },

+      startTime: {

+        $gte: this.stats.filters.startDate,

+        $lte: this.stats.filters.endDate

+      }

+    });

+  }

+

+

+  setTotalExecutions(event){

+    this.totalExecutions = event;

+  }

+}

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
index 0000000..3ca6d4f
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads-routing.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { VirtualTestHeadsComponent } from './virtual-test-heads.component';

+import { VirtualTestHeadDetailsComponent } from './virtual-test-head-details/virtual-test-head-details.component';

+

+const routes: Routes = [

+  { path:'', component: VirtualTestHeadsComponent },

+  { path:':id', component: VirtualTestHeadDetailsComponent}

+];

+

+@NgModule({

+  imports: [RouterModule.forChild(routes)],

+  exports: [RouterModule]

+})

+export class VirtualTestHeadsRoutingModule { }

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
index 0000000..2c46590
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.pug
@@ -0,0 +1,80 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+  app-page-header([heading]="'Virtual Test Heads'", [icon]="'fa-edit'")

+  

+  .card-mb-12 

+    .pull-left

+      mat-form-field

+        input(matInput, name="filter", (keyup)="applyFilter($event.target.value)", placeholder="Filter")

+    .pull-right

+      button(mat-raised-button, color="primary", (click)="createTestHead()") New

+

+    div(style="width: 100%", [hidden]="!loading")

+      mat-spinner(style="margin: auto", color="primary")

+    

+    table.mat-elevation-z8(mat-table, [dataSource]="dataSource", style="width: 100%", [hidden]="loading")

+

+      ng-container(matColumnDef="name")

+        th(mat-header-cell, *matHeaderCellDef) Name

+        td(mat-cell, *matCellDef="let element", [routerLink]="['/test-heads', element._id]") {{ element.testHeadName}}

+

+      ng-container(matColumnDef="description")

+        th(mat-header-cell, *matHeaderCellDef) Description

+        td(mat-cell, *matCellDef="let element", [routerLink]="['/test-heads', element._id]") {{ element.testHeadDescription}}

+

+      ng-container(matColumnDef="options")

+        th(mat-header-cell, *matHeaderCellDef) Options

+        td(mat-cell, *matCellDef="let element")

+          button.mr-3(mat-mini-fab, aria-label='Edit', color="primary", (click)='editTestHead(element)')

+            i.fa.fa-pencil

+          button.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='deleteTestHead(element)')

+            i.fa.fa-remove

+

+      tr(mat-header-row, *matHeaderRowDef="displayedColumns")

+      tr(mat-row, *matRowDef="let row; columns: displayedColumns")

+

+    mat-paginator([length]="resultsLength", [pageSizeOptions]="[10, 25, 100]", [hidden]="loading")

+

+    //.card-body

+      .row

+        div.col-6

+          input.form-control.bg-light.mb-1([(ngModel)]="search.test_head_id", type="text", placeholder="Search...")

+        div.col-6

+          button.bg-primary.mbtn.pull-right.text-white.mb-1(mat-raised-button, (click)='createTestHead()') Create VTH

+      table.table.table-striped([mfData]='data', #mf='mfDataTable', [mfRowsOnPage]='5')

+        thead

+          tr

+            th(style='width: 20%')

+              mfDefaultSorter(by='name') Name

+            th(style='width: 50%')

+              mfDefaultSorter(by='creator') Creator

+            th(style='width: 10%')

+              mfDefaultSorter(by='date') Date 

+            th(style='width: 20%') Options   

+        tbody

+          tr

+            td Ping Test Head

+            td Tiffany, Patrick 

+            td 7/21/18

+            td 

+              button.bg-primary.mbtn.text-white.mr-1(mat-mini-fab, aria-label='View', (click)='viewTestHead(null)') 

+                i.fa.fa-eye

+              button.bg-primary.mbtn.text-white.mr-1(mat-mini-fab, aria-label='Edit', (click)='editTestHead()')

+                i.fa.fa-pencil

+              button.mbtn.text-white(mat-mini-fab, aria-label='Remove', color='warn', (click)='deleteTestHead()')

+                i.fa.fa-remove

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
index 0000000..03c9b55
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.scss
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.mbtn:focus {

+	outline: none;

+}

+.mat-warn {

+    background-color: red;

+    color:red;

+}

+.bg-accent{

+    background-color: brown

+}

+.mat-mini-fab{

+    width: 30px !important;

+    height: 30px !important;

+    line-height: 10px !important;

+}

+

+tr:hover {background-color: #ddd;}

+

+.card-header{

+    font-size: large;

+    font-family: "Arial Black", Gadget, sans-serif; 

+}

+

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
index 0000000..5b6b605
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { VirtualTestHeadsComponent } from './virtual-test-heads.component';

+

+describe('VirtualTestHeadsComponent', () => {

+  let component: VirtualTestHeadsComponent;

+  let fixture: ComponentFixture<VirtualTestHeadsComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ VirtualTestHeadsComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(VirtualTestHeadsComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..0853862
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.component.ts
@@ -0,0 +1,145 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Output, Input, ViewChild, ChangeDetectorRef, OnDestroy } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { AppGlobals } from '../../app.global';

+import { routerTransition } from '../../router.animations';

+import { ListService } from '../../shared/services/list.service';

+import { Router } from '@angular/router';

+import { MatTableDataSource, MatPaginator, MatSort, MatDialog, MatSnackBar } from '@angular/material';

+import { TestHeadService } from '../../shared/services/test-head.service';

+import { TestHeadModalComponent } from '../../shared/modules/test-head-modal/test-head-modal.component';

+import { AlertModalComponent } from '../../shared/modules/alert-modal/alert-modal.component';

+import { AlertSnackbarComponent } from 'app/shared/modules/alert-snackbar/alert-snackbar.component';

+import { GroupService } from 'app/shared/services/group.service';

+import { Subscription } from 'rxjs';

+

+@Component({

+  selector: 'app-virtual-test-heads',

+  templateUrl: './virtual-test-heads.component.pug',

+  providers: [AppGlobals],

+  styleUrls: ['./virtual-test-heads.component.scss'],

+  animations: [routerTransition()]

+})

+export class VirtualTestHeadsComponent implements OnInit, OnDestroy {

+

+  private toDestroy: Array<Subscription> = [];

+  public dataSource;

+  public displayedColumns: string[] = ['name', 'description', 'options'];

+  public resultsLength;

+  public loading = false;

+

+  @ViewChild(MatPaginator) paginator: MatPaginator;

+

+  constructor(private testHead: TestHeadService,

+    private router: Router,

+    private modal: MatDialog,

+    private snack: MatSnackBar,

+    private _groups: GroupService) { }

+

+  applyFilter(filterValue: string) {

+    this.dataSource.filter = filterValue.trim().toLowerCase();

+  }

+

+

+  ngOnInit() {

+    this.setComponentData(this._groups.getGroup());

+    this.toDestroy.push(this._groups.groupChange().subscribe(group => {

+      this.setComponentData(group);

+    }));

+  }

+

+  ngOnDestroy(){

+    this.toDestroy.forEach(e => e.unsubscribe());

+  }

+

+  setComponentData(group) {

+    if(group){

+      

+      this.dataSource = new MatTableDataSource();

+      this.dataSource.paginator = this.paginator;

+

+      this.testHead.find({ $limit: -1, groupId: group['_id'], $sort: { createdAt: -1 } }).subscribe((list) => {

+        this.dataSource.data = list;

+        this.resultsLength = this.dataSource.data.length;

+        this.loading = false;

+      })

+    }

+  }

+

+  createTestHead() {

+    const create = this.modal.open(TestHeadModalComponent, {

+      width: '90%',

+      data: {

+        goal: 'create'

+      }

+    })

+

+    create.afterClosed().subscribe(result => {

+      this.ngOnInit();

+      // this.list.listMap['vth'].currentList.subscribe(x => {

+      //   this.dataSource.data = x;

+      //   this.resultsLength = this.dataSource.data.length;

+      // });

+    });

+  }

+

+

+  editTestHead(th) {

+    const edit = this.modal.open(TestHeadModalComponent, {

+      width: '90%',

+      data: {

+        goal: 'edit',

+        testHead: th

+      }

+    });

+

+    edit.afterClosed().subscribe(result => {

+      this.ngOnInit();

+    });

+  }

+

+  deleteTestHead(th) {

+    const deleter = this.modal.open(AlertModalComponent, {

+      width: '250px',

+      data: {

+        type: 'confirmation',

+        message: 'Are you sure you want to delete ' + th.testHeadName + '? There may be test definitions using this test head.'

+      }

+    });

+

+    deleter.afterClosed().subscribe(result => {

+      if (result) {

+        this.testHead.delete(th._id).subscribe(response => {

+          this.snack.openFromComponent(AlertSnackbarComponent, {

+            duration: 1500,

+            data: {

+              message: 'Test Head Deleted'

+            }

+          });

+

+          this.ngOnInit();

+        });

+      }

+    });

+  }

+

+  navToTestHead(id){

+    this.router.navigate(['/test-heads', id]);

+  }

+

+}

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
index 0000000..1eeb733
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { VirtualTestHeadsModule } from './virtual-test-heads.module';

+

+describe('VirtualTestHeadsModule', () => {

+  let virtualTestHeadsModule: VirtualTestHeadsModule;

+

+  beforeEach(() => {

+    virtualTestHeadsModule = new VirtualTestHeadsModule();

+  });

+

+  it('should create an instance', () => {

+    expect(virtualTestHeadsModule).toBeTruthy();

+  });

+});

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
index 0000000..a394353
--- /dev/null
+++ b/otf-frontend/client/src/app/layout/virtual-test-heads/virtual-test-heads.module.ts
@@ -0,0 +1,78 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {NgModule} from '@angular/core';

+import {CommonModule} from '@angular/common';

+

+import {VirtualTestHeadsRoutingModule} from './virtual-test-heads-routing.module';

+import {VirtualTestHeadsComponent} from './virtual-test-heads.component';

+import {PageHeaderModule} from '../../shared';

+import {FilterPipeModule} from 'ngx-filter-pipe';

+import {FormsModule} from '@angular/forms';

+import {

+    MAT_DIALOG_DATA,

+    MatButtonModule,

+    MatFormFieldModule,

+    MatInputModule,

+    MatPaginatorModule,

+    MatSnackBarModule,

+    MatProgressSpinnerModule,

+    MatDatepickerModule,

+    MatIconModule,

+    MatNativeDateModule

+} from '@angular/material';

+import {CreateTestHeadFormModule} from '../../shared/modules/create-test-head-form/create-test-head-form.module';

+import {MatTableModule} from '@angular/material/table';

+import {TestHeadModalModule} from '../../shared/modules/test-head-modal/test-head-modal.module';

+import {AlertModalModule} from '../../shared/modules/alert-modal/alert-modal.module';

+import {AlertSnackbarModule} from 'app/shared/modules/alert-snackbar/alert-snackbar.module';

+import { VirtualTestHeadDetailsComponent } from './virtual-test-head-details/virtual-test-head-details.component';

+import {MatDividerModule} from '@angular/material/divider';

+import {MatCardModule} from '@angular/material/card';

+import { LineChartComponent } from '../components/stats/line-chart/line-chart.component';

+import { DashboardModule } from '../dashboard/dashboard.module';

+import { TestHeadExecutionsLineChartComponent } from './virtual-test-head-details/charts/test-head-executions-line-chart/test-head-executions-line-chart.component';

+

+@NgModule({

+    imports: [

+        CommonModule,

+        VirtualTestHeadsRoutingModule,

+        PageHeaderModule,

+        FormsModule,

+        FilterPipeModule,

+        CreateTestHeadFormModule,

+        MatButtonModule,

+        MatTableModule,

+        MatFormFieldModule,

+        MatInputModule,

+        MatPaginatorModule,

+        TestHeadModalModule,

+        AlertModalModule,

+        MatSnackBarModule,

+        AlertSnackbarModule,

+        MatProgressSpinnerModule,

+        MatDividerModule,

+        MatCardModule,

+        MatDatepickerModule,

+        MatNativeDateModule,

+        MatIconModule

+    ],

+    declarations: [VirtualTestHeadsComponent, VirtualTestHeadDetailsComponent, TestHeadExecutionsLineChartComponent],

+    entryComponents: [],

+    providers: [{provide: MAT_DIALOG_DATA, useValue: {}}, MatDatepickerModule]

+})

+export class VirtualTestHeadsModule {

+}

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
index 0000000..04ea965
--- /dev/null
+++ b/otf-frontend/client/src/app/login/login-routing.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { LoginComponent } from './login.component';

+

+const routes: Routes = [

+    {

+        path: '',

+        component: LoginComponent

+    }

+];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class LoginRoutingModule {}

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
index 0000000..7bb3328
--- /dev/null
+++ b/otf-frontend/client/src/app/login/login.component.html
@@ -0,0 +1,46 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<div class="login-page" [@routerTransition]>

+    <div class="row justify-content-md-center">

+        <div class="col-md-4">

+            <img src="assets/images/NetworkLogo.jpg" width="200px" class="user-avatar" />

+            <h1>Open Test Framework</h1>

+            <form #login="ngForm" (ngSubmit)="onLoggedin()" role="form">

+                <div class="form-content">

+                    <div class="form-group">

+                        <input type="text" [(ngModel)]="User.email" [ngModelOptions]="{standalone: true}" class="form-control input-underline input-lg" id="email" #email="ngModel" placeholder="Email">

+                    </div>

+                    

+

+                    <div class="form-group">

+                        <input type="password" [(ngModel)]="User.password" [ngModelOptions]="{standalone: true}" class="form-control input-underline input-lg" id="password"  placeholder="Password">

+                        

+                    </div>

+                    <div *ngIf="loginFailed && (email.dirty || email.touched)"

+                            class="alert-danger">

+                            <div>

+                                Username/Password is incorrect.

+                            </div>

+                    </div>

+                </div>

+                <button class="btn rounded-btn" type="submit" id="login"> Log in </button>

+                &nbsp;

+                <a class="btn rounded-btn" [routerLink]="['/signup']">Register</a>

+            </form>

+        </div>

+    </div>

+</div>

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
index 0000000..215e85f
--- /dev/null
+++ b/otf-frontend/client/src/app/login/login.component.scss
@@ -0,0 +1,112 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+$topnav-background-color: #222;

+:host {

+    display: block;

+}

+.login-page {

+    position: absolute;

+    top: 0;

+    left: 0;

+    right: 0;

+    bottom: 0;

+    overflow: auto;

+    background: $topnav-background-color;

+    text-align: center;

+    color: #fff;

+    padding: 3em;

+    .col-lg-4 {

+        padding: 0;

+    }

+    .input-lg {

+        height: 46px;

+        padding: 10px 16px;

+        font-size: 18px;

+        line-height: 1.3333333;

+        border-radius: 0;

+    }

+    .input-underline {

+        background: 0 0;

+        border: none;

+        box-shadow: none;

+        border-bottom: 2px solid rgba(255, 255, 255, 0.5);

+        color: #fff;

+        border-radius: 0;

+    }

+    .input-underline:focus {

+        border-bottom: 2px solid #fff;

+        box-shadow: none;

+    }

+    .rounded-btn {

+        -webkit-border-radius: 50px;

+        border-radius: 50px;

+        color: rgba(255, 255, 255, 0.8);

+        background: $topnav-background-color;

+        border: 2px solid rgba(255, 255, 255, 0.8);

+        font-size: 18px;

+        line-height: 40px;

+        padding: 0 25px;

+    }

+    .rounded-btn:hover,

+    .rounded-btn:focus,

+    .rounded-btn:active,

+    .rounded-btn:visited {

+        color: rgba(255, 255, 255, 1);

+        border: 2px solid rgba(255, 255, 255, 1);

+        outline: none;

+    }

+

+    h1 {

+        font-weight: 300;

+        margin-top: 20px;

+        margin-bottom: 10px;

+        font-size: 36px;

+        small {

+            color: rgba(255, 255, 255, 0.7);

+        }

+    }

+

+    .form-group {

+        padding: 8px 0;

+        input::-webkit-input-placeholder {

+            color: rgba(255, 255, 255, 0.6) !important;

+        }

+

+        input:-moz-placeholder {

+            /* Firefox 18- */

+            color: rgba(255, 255, 255, 0.6) !important;

+        }

+

+        input::-moz-placeholder {

+            /* Firefox 19+ */

+            color: rgba(255, 255, 255, 0.6) !important;

+        }

+

+        input:-ms-input-placeholder {

+            color: rgba(255, 255, 255, 0.6) !important;

+        }

+    }

+    .form-content {

+        padding: 30px 0;

+    }

+    .user-avatar {

+        -webkit-border-radius: 50%;

+        border-radius: 50%;

+        border: 2px solid #fff;

+    }

+

+}

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
index 0000000..2eb7b85
--- /dev/null
+++ b/otf-frontend/client/src/app/login/login.component.spec.ts
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing'

+import { RouterTestingModule } from '@angular/router/testing'

+import { BrowserAnimationsModule } from '@angular/platform-browser/animations'

+

+import { LoginComponent } from './login.component'

+import { LoginModule } from './login.module'

+

+describe('LoginComponent', () => {

+  let component: LoginComponent;

+  let fixture: ComponentFixture<LoginComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      imports: [

+        LoginModule,

+        RouterTestingModule,

+        BrowserAnimationsModule,

+      ],

+    })

+    .compileComponents()

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(LoginComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges()

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy()

+  })

+});

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
index 0000000..3a17aad
--- /dev/null
+++ b/otf-frontend/client/src/app/login/login.component.ts
@@ -0,0 +1,102 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { Router, ActivatedRoute } from '@angular/router';

+import { routerTransition } from '../router.animations';

+import { HttpClient } from '@angular/common/http';

+import { AppGlobals } from '../app.global';

+import { UserService } from '../shared/services/user.service';

+import { CookieService } from 'ngx-cookie-service';

+import { AuthService } from 'app/shared/services/auth.service';

+import { AlertModalComponent } from '../shared/modules/alert-modal/alert-modal.component';

+import { MatDialog } from '@angular/material';

+

+

+

+@Component({

+    selector: 'app-login',

+    templateUrl: './login.component.html',

+    providers: [],

+    styleUrls: ['./login.component.scss'],

+    animations: [routerTransition()]

+})

+export class LoginComponent implements OnInit {

+    public User;

+    public authResult;

+    public returnUrl;

+    public loginFailed = false;

+

+    constructor(public router: Router, 

+        private route: ActivatedRoute,

+        private http: HttpClient,

+        private  _global: AppGlobals,

+        private cookie: CookieService,

+        private dialog: MatDialog,

+        private auth: AuthService

+    ) {}

+

+    ngOnInit() {

+        this.User={};

+        this.User.email = "";

+        this.User.password = "";

+        this.authResult={};

+

+        this.auth.logout();

+

+        this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';

+    }

+

+    onLoggedin() {

+        //alert("User email: " + this.User.email + " User password: " + this.User.password);

+        this.auth.login(this.User) //need to use /authorization

+            .subscribe(

+                (authResult) => {

+                    if(this.cookie.check('access_token')){

+                        this.router.navigate([this.returnUrl]);

+                    }else {

+                        if (authResult['user'] && !authResult['user']['enabled']) {

+                            this.dialog.open(AlertModalComponent, {

+                                width: '450px',

+                                data: {

+                                    type: 'ok',

+                                    message: "Your account is not yet enabled. Please wait for approval."

+                                }

+                            });

+                        }

+                        else {

+                            this.dialog.open(AlertModalComponent, {

+                                width: '450px',

+                                data: {

+                                    type: 'alert',

+                                    message: "Something went wrong... Please Refresh and try again."

+                                }

+                            });

+                        }

+                    }

+                },

+                (error) => {

+                    this.loginFailed = true;

+                    this.dialog.open(AlertModalComponent, {

+                        width: '450px',

+                        data: {

+                            type: 'alert',

+                            message: error + " Please try again"

+                        }

+                    });

+                });

+    }

+}

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
index 0000000..c4b2ce2
--- /dev/null
+++ b/otf-frontend/client/src/app/login/login.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { LoginModule } from './login.module';

+

+describe('LoginModule', () => {

+    let loginModule: LoginModule;

+

+    beforeEach(() => {

+        loginModule = new LoginModule();

+    });

+

+    it('should create an instance', () => {

+        expect(loginModule).toBeTruthy();

+    });

+});

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
index 0000000..5b72f0c
--- /dev/null
+++ b/otf-frontend/client/src/app/login/login.module.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { FormsModule } from '@angular/forms';

+import { LoginRoutingModule } from './login-routing.module';

+import { LoginComponent } from './login.component';

+import { AlertModalModule } from '../shared/modules/alert-modal/alert-modal.module';

+import { MatDialogModule } from '@angular/material';

+

+@NgModule({

+    imports: [CommonModule, LoginRoutingModule, FormsModule, AlertModalModule, MatDialogModule],

+    declarations: [LoginComponent]

+})

+export class LoginModule {}

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
index 0000000..f7efefd
--- /dev/null
+++ b/otf-frontend/client/src/app/not-found/not-found-routing.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { NotFoundComponent } from './not-found.component';

+

+const routes: Routes = [

+    {

+        path: '', component: NotFoundComponent

+    }

+];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class NotFoundRoutingModule {

+}

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
index 0000000..6a98bbd
--- /dev/null
+++ b/otf-frontend/client/src/app/not-found/not-found.component.html
@@ -0,0 +1,35 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<link href='https://fonts.googleapis.com/css?family=Anton|Passion+One|PT+Sans+Caption' rel='stylesheet' type='text/css'>

+<body>

+

+        <!-- Error Page -->

+            <div class="error">

+                <div class="container-floud">

+                    <div class="col-xs-12 ground-color text-center">

+                        <div class="container-error-404">

+                            <div class="clip"><div class="shadow"><span class="digit thirdDigit"></span></div></div>

+                            <div class="clip"><div class="shadow"><span class="digit secondDigit"></span></div></div>

+                            <div class="clip"><div class="shadow"><span class="digit firstDigit"></span></div></div>

+                            <div class="msg">OH!<span class="triangle"></span></div>

+                        </div>

+                        <h2 class="h1">Sorry! Page not found</h2>

+                    </div>

+                </div>

+            </div>

+        <!-- Error Page -->

+</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
index 0000000..1c72049
--- /dev/null
+++ b/otf-frontend/client/src/app/not-found/not-found.component.pug
@@ -0,0 +1,20 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+.col-md-12.text-center

+    img(src="assets/images/404image.png", width="400px")

+    h2 Page Not Found

+    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
index 0000000..26188f7
--- /dev/null
+++ b/otf-frontend/client/src/app/not-found/not-found.component.scss
@@ -0,0 +1,241 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+*

+{

+  font-family: 'PT Sans Caption', sans-serif, 'arial', 'Times New Roman';

+}

+/* Error Page */

+    .error .clip .shadow

+    {

+        height: 180px;  /*Contrall*/

+    }

+    .error .clip:nth-of-type(2) .shadow

+    {

+        width: 130px;   /*Contrall play with javascript*/ 

+    }

+    .error .clip:nth-of-type(1) .shadow, .error .clip:nth-of-type(3) .shadow

+    {

+        width: 250px; /*Contrall*/

+    }

+    .error .digit

+    {

+        width: 150px;   /*Contrall*/

+        height: 150px;  /*Contrall*/

+        line-height: 150px; /*Contrall*/

+        font-size: 120px;

+        font-weight: bold;

+    }

+    .error h2   /*Contrall*/

+    {

+        font-size: 32px;

+    }

+    .error .msg /*Contrall*/

+    {

+        top: -190px;

+        left: 30%;

+        width: 80px;

+        height: 80px;

+        line-height: 80px;

+        font-size: 32px;

+    }

+    .error span.triangle    /*Contrall*/

+    {

+        top: 70%;

+        right: 0%;

+        border-left: 20px solid #535353;

+        border-top: 15px solid transparent;

+        border-bottom: 15px solid transparent;

+    }

+

+

+    .error .container-error-404

+    {

+      margin-top: 10%;

+        position: relative;

+        height: 250px;

+        padding-top: 40px;

+    }

+    .error .container-error-404 .clip

+    {

+        display: inline-block;

+        transform: skew(-45deg);

+    }

+    .error .clip .shadow

+    {

+        

+        overflow: hidden;

+    }

+    .error .clip:nth-of-type(2) .shadow

+    {

+        overflow: hidden;

+        position: relative;

+        box-shadow: inset 20px 0px 20px -15px rgba(150, 150, 150,0.8), 20px 0px 20px -15px rgba(150, 150, 150,0.8);

+    }

+    

+    .error .clip:nth-of-type(3) .shadow:after, .error .clip:nth-of-type(1) .shadow:after

+    {

+        content: "";

+        position: absolute;

+        right: -8px;

+        bottom: 0px;

+        z-index: 9999;

+        height: 100%;

+        width: 10px;

+        background: linear-gradient(90deg, transparent, rgba(173,173,173, 0.8), transparent);

+        border-radius: 50%;

+    }

+    .error .clip:nth-of-type(3) .shadow:after

+    {

+        left: -8px;

+    }

+    .error .digit

+    {

+        position: relative;

+        top: 8%;

+        color: white;

+        background: #07B3F9;

+        border-radius: 50%;

+        display: inline-block;

+        transform: skew(45deg);

+    }

+    .error .clip:nth-of-type(2) .digit

+    {

+        left: -10%;

+    }

+    .error .clip:nth-of-type(1) .digit

+    {

+        right: -20%;

+    }.error .clip:nth-of-type(3) .digit

+    {

+        left: -20%;

+    }    

+    .error h2

+    {

+        color: #A2A2A2;

+        font-weight: bold;

+        padding-bottom: 20px;

+    }

+    .error .msg

+    {

+        position: relative;

+        z-index: 9999;

+        display: block;

+        background: #535353;

+        color: #A2A2A2;

+        border-radius: 50%;

+        font-style: italic;

+    }

+    .error .triangle

+    {

+        position: absolute;

+        z-index: 999;

+        transform: rotate(45deg);

+        content: "";

+        width: 0; 

+        height: 0; 

+    }

+

+/* Error Page */

+@media(max-width: 767px)

+{

+    /* Error Page */

+            .error .clip .shadow

+            {

+                height: 100px;  /*Contrall*/

+            }

+            .error .clip:nth-of-type(2) .shadow

+            {

+                width: 80px;   /*Contrall play with javascript*/ 

+            }

+            .error .clip:nth-of-type(1) .shadow, .error .clip:nth-of-type(3) .shadow

+            {

+                width: 100px; /*Contrall*/

+            }

+            .error .digit

+            {

+                width: 80px;   /*Contrall*/

+                height: 80px;  /*Contrall*/

+                line-height: 80px; /*Contrall*/

+                font-size: 52px;

+            }

+            .error h2   /*Contrall*/

+            {

+                font-size: 24px;

+            }

+            .error .msg /*Contrall*/

+            {

+                top: -110px;

+                left: 15%;

+                width: 40px;

+                height: 40px;

+                line-height: 40px;

+                font-size: 18px;

+            }

+            .error span.triangle    /*Contrall*/

+            {

+                top: 70%;

+                right: -3%;

+                border-left: 10px solid #535353;

+                border-top: 8px solid transparent;

+                border-bottom: 8px solid transparent;

+            }

+.error .container-error-404

+  {

+    height: 150px;

+  }

+        /* Error Page */

+}

+

+/*--------------------------------------------Framework --------------------------------*/

+

+.overlay { position: relative; z-index: 20; } /*done*/

+    .ground-color { background: white; }  /*done*/

+    .item-bg-color { background: #EAEAEA } /*done*/

+    

+    /* Padding Section*/

+        .padding-top { padding-top: 10px; } /*done*/

+        .padding-bottom { padding-bottom: 10px; }   /*done*/

+        .padding-vertical { padding-top: 10px; padding-bottom: 10px; }

+        .padding-horizontal { padding-left: 10px; padding-right: 10px; }

+        .padding-all { padding: 10px; }   /*done*/

+

+        .no-padding-left { padding-left: 0px; }    /*done*/

+        .no-padding-right { padding-right: 0px; }   /*done*/

+        .no-vertical-padding { padding-top: 0px; padding-bottom: 0px; }

+        .no-horizontal-padding { padding-left: 0px; padding-right: 0px; }

+        .no-padding { padding: 0px; }   /*done*/

+    /* Padding Section*/

+

+    /* Margin section */

+        .margin-top { margin-top: 10px; }   /*done*/

+        .margin-bottom { margin-bottom: 10px; } /*done*/

+        .margin-right { margin-right: 10px; } /*done*/

+        .margin-left { margin-left: 10px; } /*done*/

+        .margin-horizontal { margin-left: 10px; margin-right: 10px; } /*done*/

+        .margin-vertical { margin-top: 10px; margin-bottom: 10px; } /*done*/

+        .margin-all { margin: 10px; }   /*done*/

+        .no-margin { margin: 0px; }   /*done*/

+

+        .no-vertical-margin { margin-top: 0px; margin-bottom: 0px; }

+        .no-horizontal-margin { margin-left: 0px; margin-right: 0px; }

+

+        .inside-col-shrink { margin: 0px 20px; }    /*done - For the inside sections that has also Title section*/ 

+    /* Margin section */

+

+    hr

+    { margin: 0px; padding: 0px; border-top: 1px dashed #999; }

+/*--------------------------------------------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
index 0000000..ba9de87
--- /dev/null
+++ b/otf-frontend/client/src/app/not-found/not-found.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { NotFoundComponent } from './not-found.component';

+

+describe('NotFoundComponent', () => {

+  let component: NotFoundComponent;

+  let fixture: ComponentFixture<NotFoundComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ NotFoundComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(NotFoundComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..1aa6b9e
--- /dev/null
+++ b/otf-frontend/client/src/app/not-found/not-found.component.ts
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';

+import * as $ from 'jquery';

+

+@Component({

+  selector: 'app-not-found',

+  templateUrl: './not-found.component.html',

+  styleUrls: ['./not-found.component.scss']

+})

+export class NotFoundComponent implements OnInit {

+

+  constructor() { }

+

+  ngOnInit() {

+    var loop1, loop2, loop3, time = 30, i = 0, number, selector3 = $('.thirdDigit'), selector2 = $('.secondDigit'),

+      selector1 = $('.firstDigit');

+    loop3 = setInterval(() => {

+      "use strict";

+      if (i > 40) {

+        clearInterval(loop3);

+        selector3.text(4);

+      } else {

+        selector3.text(this.randomNum());

+        i++;

+      }

+    }, time);

+    loop2 = setInterval(() => {

+      "use strict";

+      if (i > 80) {

+        clearInterval(loop2);

+        selector2.text(0);

+      } else {

+        selector2.text(this.randomNum());

+        i++;

+      }

+    }, time);

+    loop1 = setInterval( () => {

+      "use strict";

+      if (i > 100) {

+        clearInterval(loop1);

+        selector1.text(4);

+      } else {

+        selector1.text(this.randomNum());

+        i++;

+      }

+    }, time);

+  }

+

+  randomNum() {

+    "use strict";

+    return Math.floor(Math.random() * 9) + 1;

+  }

+

+}

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
index 0000000..a24eea2
--- /dev/null
+++ b/otf-frontend/client/src/app/not-found/not-found.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NotFoundModule } from './not-found.module';

+

+describe('NotFoundModule', () => {

+  let notFoundModule: NotFoundModule;

+

+  beforeEach(() => {

+    notFoundModule = new NotFoundModule();

+  });

+

+  it('should create an instance', () => {

+    expect(notFoundModule).toBeTruthy();

+  });

+});

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
index 0000000..2bcb72f
--- /dev/null
+++ b/otf-frontend/client/src/app/not-found/not-found.module.ts
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { NotFoundRoutingModule } from './not-found-routing.module';

+import { NotFoundComponent } from './not-found.component';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    NotFoundRoutingModule

+  ],

+  declarations: [NotFoundComponent]

+})

+export class NotFoundModule { }

diff --git a/otf-frontend/client/src/app/router.animations.ts b/otf-frontend/client/src/app/router.animations.ts
new file mode 100644
index 0000000..30cf59f
--- /dev/null
+++ b/otf-frontend/client/src/app/router.animations.ts
@@ -0,0 +1,178 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { animate, group, query, state, style, transition, trigger } from '@angular/animations';

+

+export function routerTransition() {

+    return slideToTop();

+}

+

+export function routerLeftTransition() {

+    return slideToLeft();

+}

+

+export function slideToRight() {

+    return trigger('routerTransition', [

+        state('void', style({})),

+        state('*', style({})),

+        transition(':enter', [

+            style({ transform: 'translateX(-100%)' }),

+            animate('0.5s ease-in-out', style({ transform: 'translateX(0%)' }))

+        ]),

+        transition(':leave', [

+            style({ transform: 'translateX(0%)' }),

+            animate('0.5s ease-in-out', style({ transform: 'translateX(100%)' }))

+        ])

+    ]);

+}

+

+export function slideToLeft() {

+    return trigger('routerTransition', [

+        state('void', style({})),

+        state('*', style({})),

+        transition(':enter', [

+            style({ transform: 'translateX(100%)' }),

+            animate('0.5s ease-in-out', style({ transform: 'translateX(0%)' }))

+        ]),

+        transition(':leave', [

+            style({ transform: 'translateX(0%)' }),

+            animate('0.5s ease-in-out', style({ transform: 'translateX(-100%)' }))

+        ])

+    ]);

+}

+

+export function slideToBottom() {

+    return trigger('routerTransition', [

+        state('void', style({})),

+        state('*', style({})),

+        transition(':enter', [

+            style({ transform: 'translateY(-100%)' }),

+            animate('0.5s ease-in-out', style({ transform: 'translateY(0%)' }))

+        ]),

+        transition(':leave', [

+            style({ transform: 'translateY(0%)' }),

+            animate('0.5s ease-in-out', style({ transform: 'translateY(100%)' }))

+        ])

+    ]);

+}

+

+export function slideToTop() {

+    return trigger('routerTransition', [

+        state('void', style({})),

+        state('*', style({})),

+        transition(':enter', [

+            style({ transform: 'translateY(100%)' }),

+            animate('0.5s ease-in-out', style({ transform: 'translateY(0%)' }))

+        ]),

+        transition(':leave', [

+            style({ transform: 'translateY(0%)' }),

+            animate('0.5s ease-in-out', style({ transform: 'translateY(-100%)' }))

+        ])

+    ]);

+}

+

+  

+export function routerTransitionCustom() {

+    alert("");

+    return trigger('routerAnimation', [

+        state('void', style({})),

+        state('*', style({})),

+      // LEFT TO RIGHT AKA RESET

+      transition('* => 0', [

+    // Initial state of new route

+    query(':enter',

+        style({

+        position: 'fixed',

+        width: '100%',

+        transform: 'translateX(-100%)'

+        }), { optional: true }),

+    // move page off screen right on leave

+    query(':leave',

+        animate('500ms ease',

+        style({

+            position: 'fixed',

+            width: '100%',

+            transform: 'translateX(100%)',

+        })

+        ), { optional: true }),

+    // move page in screen from left to right

+    query(':enter',

+        animate('500ms ease',

+        style({

+            opacity: 1,

+            transform: 'translateX(0%)'

+        })

+        ), { optional: true }),

+    ]),

+    // LEFT TO RIGHT AKA PREVIOUS

+    transition('* => 1', [

+    // Initial state of new route

+    query(':enter',

+        style({

+        position: 'fixed',

+        width: '100%',

+        transform: 'translateX(-100%)'

+        }), { optional: true }),

+    // move page off screen right on leave

+    query(':leave',

+        animate('500ms ease',

+        style({

+            position: 'fixed',

+            width: '100%',

+            transform: 'translateX(100%)',

+        })

+        ), { optional: true }),

+    // move page in screen from left to right

+    query(':enter',

+        animate('500ms ease',

+        style({

+            opacity: 1,

+            transform: 'translateX(0%)'

+        })

+        ), { optional: true }),

+    ]),

+    // RIGHT TO LEFT AKA NEXT

+    transition('* => 2', [

+    // Initial state of new route

+    query(':enter',

+        style({

+        position: 'fixed',

+        width: '100%',

+        transform: 'translateX(100%)'

+        }), { optional: true }),

+    // move page off screen right on leave

+    query(':leave',

+        animate('500ms ease',

+        style({

+            position: 'fixed',

+            width: '100%',

+            transform: 'translateX(-100%)',

+        })

+        ), { optional: true }),

+    // move page in screen from left to right

+    query(':enter',

+        animate('500ms ease',

+        style({

+            opacity: 1,

+            transform: 'translateX(0%)'

+        })

+        ), { optional: true }),

+    ])

+    

+    ]);

+   

+}

+  
\ 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
index 0000000..90615dc
--- /dev/null
+++ b/otf-frontend/client/src/app/server-error/server-error-routing.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { ServerErrorComponent } from './server-error.component';

+

+const routes: Routes = [

+    {

+        path: '', component: ServerErrorComponent

+    }

+];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class ServerErrorRoutingModule {

+}

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
index 0000000..40b6cc4
--- /dev/null
+++ b/otf-frontend/client/src/app/server-error/server-error.component.html
@@ -0,0 +1,19 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<p>

+  server-error works!

+</p>

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/server-error/server-error.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..a0a97b8
--- /dev/null
+++ b/otf-frontend/client/src/app/server-error/server-error.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { ServerErrorComponent } from './server-error.component';

+

+describe('ServerErrorComponent', () => {

+  let component: ServerErrorComponent;

+  let fixture: ComponentFixture<ServerErrorComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ ServerErrorComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(ServerErrorComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..31b4fb6
--- /dev/null
+++ b/otf-frontend/client/src/app/server-error/server-error.component.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+

+@Component({

+  selector: 'app-server-error',

+  templateUrl: './server-error.component.html',

+  styleUrls: ['./server-error.component.scss']

+})

+export class ServerErrorComponent implements OnInit {

+

+  constructor() { }

+

+  ngOnInit() {

+  }

+

+}

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
index 0000000..c5decc5
--- /dev/null
+++ b/otf-frontend/client/src/app/server-error/server-error.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { ServerErrorModule } from './server-error.module';

+

+describe('ServerErrorModule', () => {

+  let serverErrorModule: ServerErrorModule;

+

+  beforeEach(() => {

+    serverErrorModule = new ServerErrorModule();

+  });

+

+  it('should create an instance', () => {

+    expect(serverErrorModule).toBeTruthy();

+  });

+});

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
index 0000000..835a80b
--- /dev/null
+++ b/otf-frontend/client/src/app/server-error/server-error.module.ts
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+import { ServerErrorRoutingModule } from './server-error-routing.module';

+import { ServerErrorComponent } from './server-error.component';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    ServerErrorRoutingModule

+  ],

+  declarations: [ServerErrorComponent]

+})

+export class ServerErrorModule { }

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
index 0000000..548ac7c
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.pug
@@ -0,0 +1,29 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+mat-menu(#childMenu='matMenu', [overlapTrigger]='false')

+  span(*ngFor='let child of items')

+    // Handle branch node menu items

+    span(*ngIf='child.children && child.children.length > 0')

+      button(mat-menu-item='', color='primary', (click)="sendSelected(child)", [matMenuTriggerFor]='menu.childMenu')

+        mat-icon(*ngIf="child.iconName") {{child.iconName}}

+        span {{child.displayName}}

+      app-menu-item(#menu='', [items]='child.children', (dataEvent)="receiveSelected($event)")

+    // Handle leaf node menu items

+    span(*ngIf='!child.children || child.children.length === 0')

+      button(mat-menu-item='', (click)="sendSelected(child)")

+        mat-icon(*ngIf="child.iconName") {{child.iconName}}

+        span {{child.displayName}}

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..73b7e97
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { MenuItemComponent } from './menu-item.component';

+

+describe('MenuItemComponent', () => {

+  let component: MenuItemComponent;

+  let fixture: ComponentFixture<MenuItemComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ MenuItemComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(MenuItemComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..426ca43
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/components/menu-item/menu-item.component.ts
@@ -0,0 +1,54 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, ViewChild, Output, EventEmitter } from '@angular/core';

+import { Router } from '@angular/router';

+

+export interface NavItem {

+  displayName: string;

+  disabled?: boolean;

+  iconName?: string;

+  route?: string;

+  click?: any;

+  children?: NavItem[];

+}

+

+@Component({

+  selector: 'app-menu-item',

+  templateUrl: './menu-item.component.pug',

+  styleUrls: ['./menu-item.component.scss']

+})

+

+export class MenuItemComponent implements OnInit {

+

+  @Input() items: NavItem[];

+  @ViewChild('childMenu') public childMenu;

+  @Output() dataEvent = new EventEmitter<any>();

+

+  constructor(public router: Router) { }

+

+  ngOnInit() {

+  }

+

+  receiveSelected($event){

+    this.sendSelected($event);

+  }

+

+  sendSelected(data){

+    this.dataEvent.emit(data)

+  }

+

+}

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
index 0000000..1068c95
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/factories/bpmn-factory.service.spec.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed } from '@angular/core/testing';

+

+import { BpmnFactoryService } from './bpmn-factory.service';

+

+describe('BpmnFactoryService', () => {

+  beforeEach(() => TestBed.configureTestingModule({}));

+

+  it('should be created', () => {

+    const service: BpmnFactoryService = TestBed.get(BpmnFactoryService);

+    expect(service).toBeTruthy();

+  });

+});

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
index 0000000..1a92854
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/factories/bpmn-factory.service.ts
@@ -0,0 +1,132 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { FileTransferService } from '../services/file-transfer.service';

+import { TestDefinitionService } from '../services/test-definition.service';

+import { Observable } from 'rxjs';

+import { Buffer } from 'buffer';

+import { BpmnOptions, Bpmn } from '../models/bpmn.model';

+

+interface BpmnFactoryOptions extends BpmnOptions {

+  fileId?: String,

+  testDefinitionId?: String,

+  version?: String,

+  xml?: String

+}

+

+@Injectable({

+  providedIn: 'root'

+})

+export class BpmnFactoryService {

+

+  constructor(

+    private filesTransfer: FileTransferService,

+    private testDefinition: TestDefinitionService

+  ) { }

+

+  public async setup(options: BpmnFactoryOptions): Promise<any> {

+    return new Promise(async (resolve, reject) => {

+      //check for required options

+      if (!options.mode) {

+        console.error('Bpmn options require: mode');

+        reject('Bpmn options require: mode')

+      }

+

+      let xml = await this.getXml(options);

+

+      let instance = new Bpmn(xml, {

+        mode: options.mode,

+        options: options.options

+      })

+

+      resolve(instance);

+    });

+

+  }

+

+  public async getXml(options): Promise<any> {

+    return new Promise(async (resolve, reject) => {

+      let xml;

+

+      //handle the way to retrieve bpmn xml

+      if (options.xml) {

+        xml = options.xml

+      } else if (options.fileId) {

+        xml = await this.loadFile(options.fileId);

+      } else if (options.testDefinitionId && options.version) {

+        let fileId = await this.getFileId(options.testDefinitionId, options.version);

+        xml = await this.loadFile(fileId);

+      } else if (options.testDefinitionId) {

+        let fileId = await this.getFileId(options.testDefinitionId);

+        xml = await this.loadFile(fileId);

+      } else {

+        console.warn('Either xml, fileId, testDefinitionId and version, or testDefinitionId is required to render the bpmn');

+      }

+

+      resolve(xml);

+

+    });

+

+  }

+

+  private getFileId(id, version?): Observable<Object> {

+    return new Observable(observer => {

+      this.testDefinition.get(id).subscribe(

+        data => {

+          if (data['bpmnInstances']) {

+            if (version) {

+              let index;

+              for (let i = 0; i < data['bpmnInstances'].length; i++) {

+                if (version == data['bpmnInstances'][i].version) {

+                  index = i;

+                  break;

+                }

+              }

+              if (index) {

+                observer.next(data['bpmnInstances'][index].bpmnFileId);

+              } else {

+                observer.error('No bpmn file');

+              }

+

+            } else {

+              if (data['bpmnInstances'][data['bpmnInstances'].length - 1].bpmnFileId) {

+                observer.next(data['bpmnInstances'][data['bpmnInstances'].length - 1].bpmnFileId);

+              } else {

+                observer.error('No bpmn file');

+              }

+            }

+          } else {

+            observer.error('No bpmn instances');

+          }

+        },

+        err => {

+          observer.error('No test definition found');

+        }

+      )

+    })

+  }

+

+  public loadFile(bpmnFileId) {

+    return new Promise((resolve, reject) => {

+      this.filesTransfer.get(bpmnFileId).subscribe(content => {

+        resolve(new Buffer(content as Buffer).toString());

+      }, err => {

+        reject(err);

+      });

+    });

+  }

+}

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
index 0000000..ec3d6ba
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/guard/admin.guard.spec.ts
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, async, inject } from '@angular/core/testing';

+import { RouterTestingModule } from '@angular/router/testing';

+

+import { AdminGuard } from './admin.guard';

+

+describe('AdminGuard', () => {

+    beforeEach(() => {

+        TestBed.configureTestingModule({

+            imports: [ RouterTestingModule ],

+            providers: [AdminGuard]

+        });

+    });

+

+    it('should ...', inject([AdminGuard], (guard: AdminGuard) => {

+        expect(guard).toBeTruthy();

+    }));

+});

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
index 0000000..12e2cc9
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/guard/admin.guard.ts
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

+import { HttpClient, HttpHandler, HttpHeaders } from '@angular/common/http';

+import { AppGlobals } from 'app/app.global';

+import { UserService } from '../services/user.service';

+import { CookieService } from 'ngx-cookie-service';

+

+@Injectable()

+export class AdminGuard implements CanActivate {

+

+    constructor(private router: Router, private http: HttpClient, private cookie: CookieService) { }

+

+    async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

+

+        if (this.cookie.get('access_token') && this.cookie.get('currentUser')) {

+            let currentUser = JSON.parse(this.cookie.get('currentUser'));

+            if(currentUser['permissions'].indexOf('admin') >= 0){

+                return true;

+            }

+            else{

+                this.router.navigate(['/dashboard'], { queryParams: { returnUrl: state.url }});

+                return false;

+            }

+        }

+        // not logged in so redirect to login page with the return url

+        this.router.navigate(['/dashboard'], { queryParams: { returnUrl: state.url }});

+        return false;

+

+

+

+    }

+}

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
index 0000000..a13902d
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/guard/auth.guard.spec.ts
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, async, inject } from '@angular/core/testing';

+import { RouterTestingModule } from '@angular/router/testing';

+

+import { AuthGuard } from './auth.guard';

+

+describe('AuthGuard', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      imports: [ RouterTestingModule ],

+      providers: [AuthGuard]

+    });

+  });

+

+  it('should ...', inject([AuthGuard], (guard: AuthGuard) => {

+    expect(guard).toBeTruthy();

+  }));

+});

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
index 0000000..621bb0e
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/guard/auth.guard.ts
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';

+import { HttpClient, HttpHandler, HttpHeaders } from '@angular/common/http';

+import { AppGlobals } from 'app/app.global';

+import { UserService } from '../services/user.service';

+import { CookieService } from 'ngx-cookie-service';

+

+@Injectable()

+export class AuthGuard implements CanActivate {

+

+    constructor(private router: Router, private http: HttpClient, private cookie: CookieService) { }

+

+    async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

+        if (this.cookie.check('access_token') && window.localStorage.getItem('access_token')) {

+            return true;

+        }

+

+        // not logged in so redirect to login page with the return url

+        this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});

+        return false;

+    }

+}
\ 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
index 0000000..30a32cf
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/guard/index.ts
@@ -0,0 +1,18 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+export * from './auth.guard';

+export * from './admin.guard';

diff --git a/otf-frontend/client/src/app/shared/index.ts b/otf-frontend/client/src/app/shared/index.ts
new file mode 100644
index 0000000..edc253b
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/index.ts
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+export * from './modules';

+export * from './pipes/shared-pipes.module';

+export * from './guard';

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
index 0000000..a89a591
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/models/base-model.model.ts
@@ -0,0 +1,23 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+export interface BaseModel {

+    _id: String;

+    createdAt: String;

+    createdBy: String;

+    updatedAt: String;

+    updatedBy: String;

+}
\ 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
index 0000000..ab274f8
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/models/bpmn.model.ts
@@ -0,0 +1,159 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import Modeler from 'bpmn-js/lib/Modeler';

+import Viewer from 'bpmn-js/lib/NavigatedViewer';

+import { FileTransferService } from 'app/shared/services/file-transfer.service';

+import { Observable } from 'rxjs';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+

+import { saveAs } from 'file-saver';

+//import { parseString } from 'xml2js';

+import { HostListener } from '@angular/core';

+

+export interface BpmnOptions {

+    mode: 'viewer' | 'modeler',

+    options: {

+        container: any

+    }

+}

+

+export class Bpmn {

+

+    protected model: any;

+    protected bpmnXml: String;

+    private options: BpmnOptions;

+

+    constructor(bpmnXml: String, options: BpmnOptions) {

+        //check for required options

+        if (!options.mode) {

+            console.error('Bpmn options require: mode');

+        }

+

+        this.bpmnXml = bpmnXml;

+        this.options = options;

+

+        //setup model

+        this.setModel();

+

+        //render diagram

+        this.renderDiagram();

+    }

+

+    // Getters

+

+    public getModel() {

+        return this.model;

+    }

+

+    public async getBpmnXml() {

+        return new Promise((resolve, reject) => {

+            this.model.saveXML({ format: true }, function (err, xml) {

+                if(err){

+                    reject(err);

+                }

+                resolve(xml);

+            })

+        });

+    }

+

+    // Setters

+

+    private setModel(options?) {

+

+        if (this.model) {

+            return -1;

+        }

+

+        let op = this.options.options;

+

+        if (options) {

+            op = options;

+        }

+

+        if (!op) {

+            console.error('Options for the viewer/modeler must be provided');

+            return -1;

+        }

+

+        //handle the mode (viewer or modeler)

+        switch (this.options.mode.toLowerCase()) {

+            case 'viewer':

+                this.model = new Viewer(op);

+                break;

+

+            case 'modeler':

+                this.model = new Modeler(op);

+                break;

+

+            default:

+                console.error('Mode must either be "viewer" or "modeler"');

+                return;

+        }

+

+    }

+

+    public async setBpmnXml(xml) {

+        this.bpmnXml = xml;

+        await this.renderDiagram();

+    }

+

+    // Methods

+

+    public async renderDiagram() {

+        return new Promise((resolve, reject) => {

+            if (this.bpmnXml) {

+                this.model.importXML(this.bpmnXml, (err) => {

+                    if (!err) {

+                        this.model.get('canvas').zoom('fit-viewport');

+                        resolve(true)

+                    } else {

+                        console.error(err);

+                        resolve(false);

+                    }

+                });

+            }

+        })

+    }

+

+    public resize() {

+        this.model.get('canvas').zoom('fit-viewport');

+    }

+

+    public download(saveName?) {

+

+        this.model.saveXML({ format: true }, function (err, xml) {

+            if (!saveName) {

+                let parser = new DOMParser();

+                let xmlDoc = parser.parseFromString(xml.toString(), "text/xml");

+

+                let id = xmlDoc.getElementsByTagName("bpmn:process")[0].attributes.getNamedItem("id").value;

+                

+                if (id) {

+                    saveName = id;

+                } else {

+                    saveName = 'workflow';

+                }

+            }

+

+            saveName += ".bpmn";

+

+            let blob = new Blob([xml], { type: "application/xml" });

+            saveAs(blob, saveName);

+        })

+    }

+

+}
\ 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
index 0000000..86538fa
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/models/group.model.ts
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { BaseModel } from "./base-model.model";

+

+export interface Group extends BaseModel{

+

+    groupName: String;

+    groupDescription: String;

+    parentGroupId: String;

+    ownerId: String;

+    mechanizedIds: Array<String>;

+

+}

+

+

+export class Groups implements Group {

+    groupName: String;

+    groupDescription: String;

+    parentGroupId: String;

+    ownerId: String;

+    mechanizedIds: String[];

+    _id: String;

+    createdAt: String;

+    createdBy: String;

+    updatedAt: String;

+    updatedBy: String;

+

+    static get modelName(){

+        return 'groups';

+    }

+

+    constructor(group){

+        this._id = group._id;

+        this.groupName = group.groupName;

+        this.parentGroupId = group.parentGroupId;

+    }

+}
\ 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
index 0000000..c996761
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/models/test-definition.model.ts
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { BaseModel } from "./base-model.model";

+

+export interface TestDefinition extends BaseModel {

+    

+    testName: String;

+    testDescription: String;

+    processDefinitionKey: String;

+    groupId: String;

+

+    bpmnInstances: Array<BpmnInstance>;

+

+    disabled: Boolean;

+

+}

+

+export interface BpmnInstance {

+    processDefinitionId: String;

+    deploymentId: String;

+    version: String;

+    bpmnFileId: String;

+    resourceFileId: String;

+    isDeployed: Boolean;

+

+    testHeads: Array<TestHeadRef>;

+    pflos: Array<Pflow>;

+

+    testDataTemplate: Object;

+

+    updatedBy: String;

+    createdBy: String;

+    createdAt: String;

+    updatedAt: String;

+}

+

+export interface TestHeadRef {

+    testHeadId: String;

+    bpmnVthTaskId: String;

+    label: String;

+}

+

+export interface Pflow {

+    bpmnPflowTaskId: String;

+    label: String;

+}
\ 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
index 0000000..9f410b5
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/models/test-execution.model.ts
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestDefinition } from "./test-definition.model";

+import { TestInstance } from "./test-instance.model";

+

+export interface TestExecution {

+

+    _id: String;

+    processInstanceId: String;

+    businessKey: String;

+    testResult: String;

+    testDetails: Object;

+    startTime: Date;

+    endTime: Date;

+    async: Boolean;

+    asyncTopic: String;

+    groupId: String;

+    executorId: String;

+    testHeadResults: Array<Object>;

+    testInstanceResults: Array<Object>;

+    historicEmail: String;

+    historicTestInstance: TestInstance;

+    historicTestDefinition: TestDefinition;

+

+}
\ 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
index 0000000..ef28772
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/models/test-head.model.ts
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { BaseModel } from "./base-model.model";

+

+export interface TestHead extends BaseModel {

+

+    testHeadName: String;

+    testHeadDescription: String;

+    testHeadType: String;

+    vthInputTemplate: Object;

+    vendor: String;

+    port: String;

+    hostname: String;

+    resourcePath: String;

+    groupId: String;

+    authorizationType: String,

+    authorizationCredential: String,

+    authorizationEnabled: Boolean,

+

+}

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
index 0000000..9bac1c0
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/models/test-instance.model.ts
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { BaseModel } from "./base-model.model";

+

+export interface TestInstance extends BaseModel {

+    

+    testInstanceName: String;

+    testInstanceDescription: String;

+    testDefinitionId: String;

+    useLatestDefinition: Boolean;

+    processDefinitionId: String;

+    testData: Object;

+    internalTestData: Object;

+    simulationMode: Boolean;

+    simulationVthInput: Object;

+    vthInput: Object;

+    pfloInput: Object;

+    disabled: Boolean;

+    maxExecutionTimeInMillis: Number;

+    groupId: String;

+

+}
\ 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
index 0000000..9e75b95
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/models/user.model.ts
@@ -0,0 +1,45 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { BaseModel } from "./base-model.model";

+

+export interface User extends BaseModel {

+

+    firstName: String;

+    lastName: String;

+    email: String;

+    permissions: Array<String>;

+    password: String;

+

+    groups: Array<GroupRef>;

+

+    favorites: Object;

+

+    enabled: Boolean;

+    isVerified: Boolean;

+    verifyToken: String;

+    verifyExpires: Date;

+    verifyChanges: Object;

+

+    resetToken: String;

+    resetExpires: DataCue;

+

+}

+

+interface GroupRef {

+    groupId: String;

+    permissions: Array<String>;

+}

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
index 0000000..1db2cd3
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.pug
@@ -0,0 +1,83 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div(*ngIf = 'type == "warning"')

+    h2(mat-dialog-title)

+        i.fa.fa-warning

+        div Warning

+    mat-dialog-content

+        p(style="text-align: center") {{data.message}}

+    mat-dialog-actions

+        button(mat-raised-button, color="primary", aria-label='View', (click) = "okay()") OK

+

+div(*ngIf = 'type == "confirmation"')

+    h2(mat-dialog-title)

+        i.fa.fa-check

+        div Confirm

+    mat-dialog-content

+        p(style="text-align: center") {{data.message}}

+    mat-dialog-actions

+        button(mat-raised-button, color="primary", aria-label='Yes', (click) = "confirmed()") Yes

+        button(mat-raised-button, color="primary", aria-label='Cancel', (click) = "canceled()") Cancel

+

+div(*ngIf = 'type == "userAdmin"')

+    h2(mat-dialog-title)

+        i.fa.fa-question

+        div Choose

+    mat-dialog-content

+        p(style="text-align: center") {{data.message}}

+    mat-dialog-actions

+        button(mat-raised-button, color="primary", aria-label='Yes', (click) = "confirmed()") Admin

+        button(mat-raised-button, color="primary", aria-label='Cancel', (click) = "canceled()") User

+

+div(*ngIf = 'type == "alert"')

+    h2(mat-dialog-title)

+        i.fa.fa-times-circle-o

+        div Error

+    mat-dialog-content

+        p(style="text-align: center") {{data.message}}

+    mat-dialog-actions

+        button.bg-primary.mbtn.text-white.mr-1(mat-raised-button, aria-label='OK', (click) = "okay()") OK

+

+div(*ngIf = 'type == "ok"')

+    h2(mat-dialog-title)

+        i.fa.fa-check

+        div Alert

+    mat-dialog-content

+        p(*ngIf="html", [innerHtml]="html")

+        p(*ngIf="!html", style="text-align: center") {{data.message}}

+    mat-dialog-actions

+        button.bg-primary.mbtn.text-white.mr-1(mat-raised-button, aria-label='OK', (click) = "okay()") OK

+

+div(*ngIf = 'type == "info"')

+    h2(mat-dialog-title)

+        div Info

+    mat-dialog-content

+        p(*ngIf="html", [innerHtml]="html")

+        p(*ngIf="!html", style="text-align: center") {{data.message}}

+    mat-dialog-actions

+        button.bg-primary.mbtn.text-white.mr-1(mat-raised-button, aria-label='OK', (click) = "okay()") OK

+

+

+

+//<h1 mat-dialog-title>Add file</h1>

+    <mat-dialog-content>

+      Content goes here

+    </mat-dialog-content>

+    <mat-dialog-actions>

+      <button mat-button>Add</button>

+      <button mat-button>Cancel</button>

+    </mat-dialog-actions>

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
index 0000000..608c6cc
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.scss
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.fa-warning{

+    color: #FFCC00;

+}

+.fa-times-circle-o{

+    color: red;

+}

+

+.fa-check {

+    color: green;

+}

+

+mat-dialog-actions .mat-raised-button {

+    margin: auto;

+}

+

+.mat-dialog-title {

+    text-align: center;

+}

+

+i {

+    font-size: 30px;

+}
\ 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
index 0000000..01ad3d6
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { AlertModalComponent } from './alert-modal.component';

+

+describe('AlertModalComponent', () => {

+  let component: AlertModalComponent;

+  let fixture: ComponentFixture<AlertModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ AlertModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(AlertModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..6904ebf
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.component.ts
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import {Component, Inject, OnInit} from '@angular/core';

+import {MAT_DIALOG_DATA, MatDialogRef,} from '@angular/material';

+

+@Component({

+    selector: 'app-alert-modal',

+    templateUrl: './alert-modal.component.pug',

+    styleUrls: ['./alert-modal.component.scss']

+})

+export class AlertModalComponent implements OnInit {

+    public data;

+    public type;

+    public html;

+

+    constructor(

+        public dialogRef: MatDialogRef<AlertModalComponent>,

+        @Inject(MAT_DIALOG_DATA) public input_data

+    ) {

+        this.data = this.input_data;

+        if (this.data.type.match(new RegExp('^warning$', 'i'))) {

+            this.type = 'warning';

+        } else if (this.data.type.match(new RegExp('^confirmation$', 'i'))) {

+            this.type = 'confirmation';

+        } else if (this.data.type.match(new RegExp('^alert$', 'i'))) {

+            this.type = 'alert';

+        } else if (this.data.type.match(new RegExp('^ok$', 'i'))) {

+            this.type = 'ok';

+        } else if (this.data.type.match(new RegExp('^userAdmin$', 'i'))) {

+            this.type = 'userAdmin';

+        } else {

+            this.type = 'info';

+        }

+    }

+

+    ngOnInit() {

+        if(this.data.html){

+            this.html = this.data.html;

+        }

+    }

+

+    okay() {

+        this.dialogRef.close();

+    }

+

+    confirmed() {

+        this.dialogRef.close(true);

+    }

+

+    canceled() {

+        this.dialogRef.close(false);

+    }

+}

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
index 0000000..bfe72d1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { AlertModalModule } from './alert-modal.module';

+

+describe('AlertModalModule', () => {

+  let alertModalModule: AlertModalModule;

+

+  beforeEach(() => {

+    alertModalModule = new AlertModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(alertModalModule).toBeTruthy();

+  });

+});

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
index 0000000..3fb4e6b
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-modal/alert-modal.module.ts
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { AlertModalComponent } from './alert-modal.component';

+import { MatDialogModule, MatButtonModule} from '@angular/material';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { FormsModule } from '@angular/forms';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    MatButtonModule,

+    MatDialogModule,

+    FilterPipeModule,

+    FormsModule

+  ],

+  declarations: [AlertModalComponent],

+  exports: [ AlertModalComponent],

+  entryComponents: [AlertModalComponent]

+})

+export class AlertModalModule { }

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
index 0000000..ea28f8f
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.pug
@@ -0,0 +1,19 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+.pull-left 

+  mat-icon(style="color: green") check

+.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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..c17a50e
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { AlertSnackbarComponent } from './alert-snackbar.component';

+

+describe('AlertSnackbarComponent', () => {

+  let component: AlertSnackbarComponent;

+  let fixture: ComponentFixture<AlertSnackbarComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ AlertSnackbarComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(AlertSnackbarComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..dd0c1d0
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.component.ts
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject } from '@angular/core';

+import { MAT_SNACK_BAR_DATA, } from '@angular/material';

+

+@Component({

+  selector: 'app-alert-snackbar',

+  templateUrl: './alert-snackbar.component.pug',

+  styleUrls: ['./alert-snackbar.component.scss']

+})

+export class AlertSnackbarComponent implements OnInit {

+

+  public data;

+

+  constructor(@Inject(MAT_SNACK_BAR_DATA) public input_data) { 

+    this.data = input_data;

+  }

+

+  ngOnInit() {

+  }

+

+}

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
index 0000000..d7eb3bc
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { AlertSnackbarModule } from './alert-snackbar.module';

+

+describe('AlertSnackbarModule', () => {

+  let alertSnackbarModule: AlertSnackbarModule;

+

+  beforeEach(() => {

+    alertSnackbarModule = new AlertSnackbarModule();

+  });

+

+  it('should create an instance', () => {

+    expect(alertSnackbarModule).toBeTruthy();

+  });

+});

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
index 0000000..5910877
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/alert-snackbar/alert-snackbar.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { AlertSnackbarComponent } from './alert-snackbar.component';

+import { MatSnackBarModule, MatIconModule} from '@angular/material';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    MatSnackBarModule,

+    MatIconModule

+  ],

+  declarations: [AlertSnackbarComponent],

+  entryComponents: [AlertSnackbarComponent]

+})

+export class AlertSnackbarModule { }

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
index 0000000..82c37a4
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.pug
@@ -0,0 +1,37 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div([@routerTransition])

+  app-page-header([heading]="'Create Group'")

+  form.ml-2(style="width:100%")

+    .row

+      .col-md-6

+        .row

+          mat-form-field.mr-2

+            mat-select([(value)]="newGroup.parentGroupId", placeholder="Parent Group", required)

+              mat-option(value="None") None

+              mat-option(*ngFor="let group of groups", [value]="group._id") {{group.groupName}} 

+              

+        .row

+          mat-form-field.mr-2(required)

+            input(matInput, placeholder="New Group Name", [(ngModel)]="newGroup.groupName", name="Group Name", required)

+        

+      .col-md-6

+        mat-form-field.mr-2

+          textarea(matInput, cdkTextareaAutosize, placeholder="Description", #autosize="cdkTextareaAutosize", cdkAutosizeMinRows="2", cdkAutosizeMaxRows="5", name="description", [(ngModel)]="newGroup.groupDescription")

+

+    button.pull-left(mat-raised-button, color="primary", (click)="createGroup()") Create

+    button.pull-right(mat-raised-button, color="warn", (click)="close()") Cancel

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..f6b0153
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { CreateGroupModalComponent } from './create-group-modal.component';

+

+describe('CreateGroupModalComponent', () => {

+  let component: CreateGroupModalComponent;

+  let fixture: ComponentFixture<CreateGroupModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ CreateGroupModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(CreateGroupModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..2d0befc
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.component.ts
@@ -0,0 +1,131 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject } from '@angular/core';

+import { MatDialogRef, MAT_DIALOG_DATA, MatSnackBar, MatDialog } from '@angular/material';

+import { GroupService } from 'app/shared/services/group.service';

+import { CookieService } from 'ngx-cookie-service';

+import { UserService } from 'app/shared/services/user.service';

+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';

+import { AlertModalComponent } from 'app/shared/modules/alert-modal/alert-modal.component';

+

+

+@Component({

+  selector: 'app-create-group-modal',

+  templateUrl: './create-group-modal.component.pug',

+  styleUrls: ['./create-group-modal.component.scss']

+})

+export class CreateGroupModalComponent implements OnInit {

+

+  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) { }

+

+  public groups;

+  public newGroup;

+  public user;

+

+  ngOnInit() {

+    this.newGroup = {};

+    this.user = {};

+    this.groups = [];

+    this.newGroup.groupName = '';

+    this.newGroup.parentGroupId = null;

+    this.user._id = this.userService.getId();

+    this.newGroup.ownerId = this.user["_id"];

+    //filter list of groups by the Admin permssion from the user

+    //Also add group onto active dropdown list when this dialog is closed

+    this.groupService.find({

+      $limit: -1

+      

+    }).subscribe((list) => {

+      //console.log(list);

+      for(let i in list){

+        //console.log(this.user._id + "     " + list[i]);

+        if(this.checkIsAdmin(list[i], this.user._id)){

+          this.groups.push(list[i]);

+        }

+      }

+      

+    });

+

+  }

+

+  checkIsAdmin(group, userId){

+    if(group.members){

+      let memberIndex =  group.members.findIndex(function(member){return member.userId.toString() == userId.toString()});

+      if(memberIndex >= 0){

+        if(group.members[memberIndex].roles.includes("admin")){

+          return true;

+        }

+      }

+    }

+    return false;

+  }

+

+  close(){

+    

+    this.dialogRef.close(null);

+  }

+

+  createGroup(){

+    

+    //console.log(this.newGroup);

+    if(this.newGroup.parentGroupId == "None"){

+      this.newGroup.parentGroupId = null;

+    }

+    

+    this.newGroup.roles = [{

+      roleName: "admin",

+      permissions: ["management", "write", "delete", "read", "execute"]

+    },

+    {

+      roleName: "user",

+      permissions: ["read"]

+    },

+    {

+      roleName: "developer",

+      permissions: ["write", "delete", "read", "execute"]

+    }];

+    this.newGroup.members = [{

+      userId: this.user._id,

+      roles: ["admin"]

+    }];

+    this.groupService.create(this.newGroup).subscribe(res => { 

+      

+      let snackMessage = 'The group ' + this.newGroup.groupName + " has been created!";

+      this.snack.openFromComponent(AlertSnackbarComponent, {

+          duration: 1500,

+          data: {

+              message: snackMessage

+          }

+      });

+      if(res){

+        this.dialogRef.close(res)

+      }else{

+        this.close();

+      }

+    }, (error) => {

+        this.modal.open(AlertModalComponent, {

+          width: "250px",

+          data: {

+              type: "alert",

+              message: error

+          }

+        });

+    }); 

+    

+  }

+

+}

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
index 0000000..91ecd63
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { CreateGroupModalModule } from './create-group-modal.module';

+

+describe('CreateGroupModalModule', () => {

+  let createGroupModalModule: CreateGroupModalModule;

+

+  beforeEach(() => {

+    createGroupModalModule = new CreateGroupModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(createGroupModalModule).toBeTruthy();

+  });

+});

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
index 0000000..42bc6e6
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-group-modal/create-group-modal.module.ts
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { CreateGroupModalComponent } from './create-group-modal.component';

+import { FormsModule } from '@angular/forms';

+import { MatButtonModule, MatInputModule, MatSelectModule, MatOptionModule, MatSnackBarModule, MatIconModule, MatDialogModule } from '@angular/material';

+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';

+import { PageHeaderModule } from '..';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FormsModule,

+    MatButtonModule,

+    MatInputModule,

+    MatSelectModule,

+    MatOptionModule,

+    MatSnackBarModule,

+    PageHeaderModule,

+    AlertSnackbarModule,

+    MatIconModule,

+    MatDialogModule

+  ],

+  declarations: [CreateGroupModalComponent],

+  exports: [ CreateGroupModalComponent],

+  entryComponents: [ CreateGroupModalComponent ]

+})

+export class CreateGroupModalModule { }

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
index 0000000..dbafe0e
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.pug
@@ -0,0 +1,167 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+form(#testDefinitionForm="ngForm")

+  .row.mb-3

+    .col-sm-6(style="justify-content: flex-end;flex-direction: column;display: flex")

+

+      //- Diagram

+      .row(style="height: 100%")

+        //- placeholder

+        .col-12(*ngIf="!ptd.currentInstance.bpmnXml", style="text-align:center; opacity: .4")

+          i.fa.fa-5x.fa-object-group

+        //- diagram

+        .col-12(#canvas, [hidden]="!ptd.currentInstance.bpmnXml", style="position: relative; cursor: pointer", (click)="enlargeBpmn()")

+          button(mat-icon-button, color="primary", style="position: absolute; top: 0px; right: 0px; z-index: 100")

+            mat-icon zoom_in

+        

+

+      //- Upload and version

+      .row

+        .col-sm-6(style="text-align:center")

+          input(id="file", #file, type="file", name="file", ng2FileSelect, [uploader]="bpmnUploader", style="display:none", [hidden]="!ptd.currentInstance.isDeployed", (change)="validateFile()", required)

+          

+          //- when creating new

+          button(mat-raised-button, color="accent", *ngIf="!ptd.currentInstance.isDeployed && !ptd.currentInstance.bpmnXml && isNew", [hidden]="isUploading", (click)="isClicked = true", onclick="file.click();")

+            | Upload Workflow

+          button(mat-raised-button, color="primary", *ngIf="!ptd.currentInstance.isDeployed && ptd.currentInstance.bpmnXml", [hidden]="isUploading", onclick="file.click();")

+            | Change Workflow

+

+          //- when editing

+          //- button(mat-raised-button, color="primary", *ngIf="!isNew && ptd.currentInstance.isDeployed", [hidden]="isUploading", (click)="newVersion(this.ptd.processDefinitionKey)", onclick="file.click();")

+          //-   | New Version

+          h4(*ngIf="ptd.currentInstance.isDeployed") Deployed

+          

+          mat-spinner(style="margin:auto", [diameter]="30", [hidden]="!isUploading")

+

+        .col-sm-6

+          mat-form-field(*ngIf="ptd.processDefinitionKey != null")

+            input(matInput, placeholder="Process Definition Key", name="processDefinitionKey", maxlength="22", [disabled]="hasBeenSaved", (keyup)="checkProcessDefinitionKey()", [(ngModel)]="ptd.processDefinitionKey", required)

+            mat-spinner(matSuffix, *ngIf="pStatus == 'loading'", [diameter]="19")

+            mat-icon(matSuffix, *ngIf="pStatus == 'unique'", style="color: green") check

+            mat-icon(matSuffix, *ngIf="pStatus == 'notUnique'", style="color: red") error_outline

+

+    .col-sm-6

+      mat-form-field(style="width:100%")

+        input(matInput, type="text", placeholder="Name", name="name", [disabled]="(existingTd && !hasBeenSaved) || !ptd.currentInstance.bpmnXml", [(ngModel)]="ptd.testName", required)

+        mat-error Required

+      mat-form-field(style="width:100%")

+        input(matInput, type="text", placeholder="Description", name="description", [disabled]="(existingTd && !hasBeenSaved) || !ptd.currentInstance.bpmnXml", [(ngModel)]="ptd.testDescription", required)

+        mat-error Required

+      //- mat-form-field(style="width:100%")

+      //-   mat-select((selectionChange)="markAsDirty()", name="ns", [disabled]="(existingTd && !hasBeenSaved) || !ptd.currentInstance.bpmnXml", placeholder="Group", [(value)]="ptd.groupId", required)

+      //-     mat-option(*ngFor="let group of groups", value="{{group._id}}") {{ group.groupName }}

+      //-   mat-error Required

+      mat-form-field(style="width:100%")

+        input(matInput, type="text", *ngIf="!hasBeenSaved", placeholder="Version", name="version", [(ngModel)]="ptd.currentInstance.version", (keyup)="checkVersionUnique()", required)

+        mat-select((selectionChange)="switchVersion(ptd.currentVersionName)", placeholder="Version", name="selectedVersion", *ngIf="hasBeenSaved", [(value)]="ptd.currentVersionName", required)

+          mat-option(*ngFor="let instance of ptd.bpmnInstances.slice().reverse()", value="{{instance.version}}") {{ instance.version }}

+        mat-error Required

+        button(mat-button, matSuffix, color="primary", *ngIf="hasBeenSaved", (click)="newVersion(this.ptd.processDefinitionKey)", onclick="file.click();") New

+

+      button(mat-button, (click)="viewer.download()", color="primary")

+        mat-icon cloud_download

+        span.ml-2 Download

+        

+      

+  .row

+    .col-12(*ngIf="ptd.currentInstance")

+      mat-accordion

+        mat-expansion-panel([expanded]="ptd.currentInstance.dataTestHeads.length > 0")

+          mat-expansion-panel-header

+            mat-panel-title Test Heads

+            mat-panel-description(*ngIf="ptd.currentInstance.dataTestHeads") {{ ptd.currentInstance.dataTestHeads.length > 0? ptd.currentInstance.dataTestHeads.length : '' }}

+          .ps(style="position: relative; max-height: 105px", [perfectScrollbar])

+            div(style="white-space: nowrap")

+              .mr-4.text-center(*ngFor=("let task of ptd.currentInstance.dataTestHeads; index as i; trackBy: trackByFn"), style="display:inline-block")

+                .text-muted {{task.bpmnVthTaskId}}

+                button(color="accent", mat-fab, (click)="selectTestHead(i)")

+                  i.fa.fw.fa-gears.fa-2x(style="color:white")

+                p.text-muted {{ (task.testHead && task.testHead.testHeadName) ? task.testHead.testHeadName : '' }}

+      mat-expansion-panel([expanded]="true")

+        mat-expansion-panel-header

+          mat-panel-title Resources

+          mat-panel-description A single .zip file with scripts

+        input(type="file", #scripts, id="scripts", name="scripts", hidden,  (change)="markAsDirty()", ng2FileSelect, [uploader]="uploader", accept="application/zip")

+        .row(*ngIf="ptd.currentInstance.resourceFileId")

+          .col-12

+            mat-list

+              mat-list-item

+                mat-icon(mat-list-icon) insert_drive_file

+                h4(mat-line) {{ptd.currentInstance.resourceFileName }}

+        .row(*ngIf="!ptd.currentInstance.isDeployed")

+          .col-md-3

+            //- .mb-2 TESTING GIT TRACKING

+            //-   | Multiple Files 

+            //-   mat-slide-toggle(color="primary", name="isZip", [(ngModel)]="isZip", (change)="uploader.clearQueue()")

+            //-   |  .zip

+            //- div 

+            //-   input(*ngIf="!isZip", type="file", name="scripts", ng2FileSelect, [uploader]="uploader", multiple)

+            

+            button(mat-raised-button, *ngIf="isZip && !ptd.currentInstance.resourceFileId", onclick="scripts.click()", color="primary") Choose File

+            button(mat-raised-button, *ngIf="isZip && ptd.currentInstance.resourceFileId", onclick="scripts.click()", color="primary") Replace File

+          .col-md-8.ml-2

+            div(*ngIf="uploader.queue.length > 0")

+              label File:

+              ul.list-group(style="position:relative")

+                li.list-group-item(*ngFor="let item of uploader.queue")

+                  | {{ item?.file?.name }}

+                  div.upload-progress([ngStyle]="{'width': item.progress + '%'}")

+              //- button.pull-right(mat-button, (click)="upload()") Upload All

+              label(*ngIf="ptd.currentInstance.resourceFileId && uploader.queue.length > 0 && !saved") This will replace the previous resouce file

+              button.pull-right(mat-button, color="primary", (click)="clearQueue()") Remove All

+        .row(*ngIf="ptd.currentInstance.isDeployed")

+          .col-12(*ngIf="!ptd.currentInstance.resourceFileId")

+            | No resources were deployed with this version

+            

+

+    .col-12(*ngIf="ptd.currentInstance.testDataTemplate != {}")

+      h5.text-muted.mt-4 testInputTemplate.yaml

+      div(style="border: 1px solid lightgrey; font-size: 16px !important")

+        codemirror([config]="codeConfig", [(ngModel)]='ptd.currentInstance.testDataTemplate', name="testConfig")

+

+  .row(style="height:30px")

+  .row.form-buttons

+    .col-12.mt-3

+      .pull-left

+        .mr-3(mat-button, *ngIf="hasBeenSaved && saved && !testDefinitionForm.dirty") saved

+          mat-icon(style="color:green") check

+      .pull-right

+        //save

+        button.mr-3(mat-raised-button, *ngIf="!hasBeenSaved && !inProgress", color="primary", [disabled]="!testDefinitionForm.form.valid || !ptd.currentInstance.bpmnXml || !successUpload || pStatus == 'notUnique'", (click)="save()") Save

+

+        //update

+        button.mr-3(mat-raised-button, *ngIf="hasBeenSaved && !inProgress", color="primary", [disabled]="!testDefinitionForm.form.valid || !ptd.currentInstance.bpmnXml  || !testDefinitionForm.dirty || !successUpload", (click)="update()") Update

+        

+        //save and deploy

+        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

+        

+        //update and deploy

+        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

+

+        //deploy

+        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

+      

+        //delete

+        button.mr-3(mat-raised-button, color="warn", *ngIf="hasBeenSaved && !inProgress", (click)="deleteVersion()") Delete Version

+

+        //- button((click)="print()") print

+        

+        //In Progress

+        button.mr-3(mat-raised-button, *ngIf="inProgress", color="primary", disabled)

+          mat-spinner([diameter]="19", style="display:inline")

+          div.ml-4(style="display:inline") In Progress

+        

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
index 0000000..124106c
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.scss
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.slider {

+	overflow-y: hidden;

+	max-height: 500px; /* approximate max height */

+

+	transition-property: all;

+	transition-duration: .5s;

+	transition-timing-function: cubic-bezier(0, 1, 0.5, 1);

+}

+

+.fa-spinner{

+	color: green;

+}

+

+.tsd {

+	width:100%; 

+	height:100%; 

+	background: orange

+}

+

+.centered {

+	position: absolute;

+	left: 50%;

+	top: 50%;

+	-webkit-transform: translate(-50%, -50%);

+	transform: translate(-50%, -50%);

+}

+

+.no-margin-tlb {

+	margin: -16px -16px -16px -16px;

+}

+

+.mbtn:focus {

+	outline: none;

+}
\ 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
index 0000000..595b3d1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { CreateTestFormComponent } from './create-test-form.component';

+

+describe('CreateTestFormComponent', () => {

+  let component: CreateTestFormComponent;

+  let fixture: ComponentFixture<CreateTestFormComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ CreateTestFormComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(CreateTestFormComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..f88523d
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.component.ts
@@ -0,0 +1,823 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, EventEmitter, Input, OnInit, Output, ViewChild, ElementRef, OnDestroy } from '@angular/core';

+import { MatDialog, MatSnackBar } from '@angular/material';

+import { SelectTestHeadModalComponent } from '../select-test-head-modal/select-test-head-modal.component';

+import { GroupService } from '../../services/group.service';

+import { TestDefinitionService } from '../../services/test-definition.service';

+import { AlertModalComponent } from '../alert-modal/alert-modal.component';

+import { Alert } from 'selenium-webdriver';

+import { ListService } from '../../services/list.service';

+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';

+import { TestHeadService } from 'app/shared/services/test-head.service';

+import { FileUploader, FileItem, ParsedResponseHeaders } from 'ng2-file-upload';

+import { AppGlobals } from 'app/app.global';

+import { HttpHeaders } from '@angular/common/http';

+import { CookieService } from 'ngx-cookie-service';

+import { stringify } from '@angular/core/src/render3/util';

+import Modeler from 'bpmn-js';

+import { FileService } from 'app/shared/services/file.service';

+import { FileTransferService } from 'app/shared/services/file-transfer.service';

+import { TestDefinition } from './test-definition.class';

+import { connectableObservableDescriptor } from 'rxjs/internal/observable/ConnectableObservable';

+import { Buffer } from 'buffer';

+import { ViewWorkflowModalComponent } from '../view-workflow-modal/view-workflow-modal.component';

+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';

+import { Bpmn } from 'app/shared/models/bpmn.model';

+

+

+@Component({

+  selector: 'app-create-test-form',

+  templateUrl: './create-test-form.component.pug',

+  styleUrls: ['./create-test-form.component.scss']

+})

+export class CreateTestFormComponent implements OnInit, OnDestroy {

+

+

+  public codeConfig = {

+    mode: 'yaml',

+    theme: 'eclipse',

+    lineNumbers: true

+  };

+

+  public trackByFn;

+

+  public selectedTestHead;

+  public groups;

+  public isUploading;

+  public successUpload = false;

+  public processDefinitionKey = false;

+  public validateResponse;

+  public pStatus;

+  public file: File;

+  public hasBeenSaved = false;

+  public saved = false;

+  public isClicked;

+  public isZip = true;

+  public viewer: Bpmn;

+  public scriptFiles = [];

+  public existingTd;

+

+  @ViewChild('testDefinitionForm') form: any;

+  @ViewChild('canvas') canvas;

+  @ViewChild('scripts') scripts: ElementRef;

+  @ViewChild('file') bpmnFileInput: ElementRef;

+

+  @Input() public listKey;

+

+  @Input() public formData;

+

+  @Output() public childEvent = new EventEmitter();

+

+  public uploader: FileUploader;

+  public bpmnUploader: FileUploader;

+

+  public inProgress = false;

+

+  // New variables

+  public ptd: TestDefinition;

+  public isNew = true;

+

+  constructor(

+    public dialog: MatDialog,

+    private list: ListService,

+    private testHead: TestHeadService,

+    private group: GroupService,

+    private testDefinition: TestDefinitionService,

+    private snack: MatSnackBar,

+    private cookie: CookieService,

+    private fileTransfer: FileTransferService,

+    private fileService: FileService,

+    private bpmnFactory: BpmnFactoryService

+    ) { }

+

+  print(){

+    console.log(this.ptd);

+  }

+

+  async ngOnInit() {

+    //this.setNew();

+

+    this.viewer = await this.bpmnFactory.setup({

+      mode: 'viewer',

+      options: {

+        container: this.canvas.nativeElement

+      }

+    })

+    

+    this.ptd = new TestDefinition();

+    this.ptd.reset();

+    this.ptd.switchVersion();

+

+    let uploadOptions = {

+      url: AppGlobals.baseAPIUrl + 'file-transfer',

+      authTokenHeader: 'Authorization',

+      authToken: 'Bearer ' + JSON.parse(this.cookie.get('access_token'))

+    };

+

+    //File Uploaders

+    this.uploader = new FileUploader(uploadOptions);

+    this.bpmnUploader = new FileUploader(uploadOptions);

+

+    if (this.formData && this.formData !== 'new') {

+      this.hasBeenSaved = true;

+      this.successUpload = true;

+      this.isNew = false;

+      this.setTestDefinition();

+    }

+

+    this.group.find({$limit: -1}).subscribe((x) => {

+      this.groups = x;

+    });

+

+  }

+

+  ngOnDestroy(){

+    

+  }

+

+  waitSave(){

+    return new Promise((resolve, reject) => {

+      console.log('waitsave')

+      //upload bpmn file

+      this.saveBpmnFile().then(bpmnFile => {

+        console.log(bpmnFile)

+        console.log('pass save bpmnfile')

+        this.checkTestDataTemplate();

+

+        let data = this.gatherTestDefinition(bpmnFile);

+        console.log(data)

+        //If this is not a new version

+        if(!this.existingTd){

+          delete data._id;

+          

+          this.create(data).then(

+            result => {

+              resolve(result);

+              this.setTestDefinition(result);

+              this.showHasBeenSaved();

+            }

+          ).catch(err => {

+            reject(err);

+            this.showHasNotBeenSaved();

+          });

+        }else{

+          //create version by updating definition

+          this.saveVersion(data).then(

+            result => {

+              resolve(result);

+              this.setTestDefinition(result);

+              this.showHasBeenSaved();

+            }

+          ).catch(err => {

+            reject(err);

+            this.showHasNotBeenSaved();

+          });

+        }

+      });

+    })

+  }

+  

+  // Saves Test Definition - Triggered by "Save" button

+  save() {

+    //set in progress

+    this.inProgress = true;

+    return this.waitSave();

+

+  }

+

+  // Updates Test Definition - Triggered by "Update" button

+  update() {

+    this.inProgress = true;

+    return this.saveBpmnFile().then(bpmnFile => {

+      this.checkTestDataTemplate();

+

+      var data = this.gatherTestDefinition(bpmnFile);

+

+      return this.testDefinition.patch(data)

+        .subscribe(

+          result => {

+            this.uploadResources(result).then(

+              res => {

+                this.setTestDefinition(res);

+                this.showHasBeenUpdated();

+              }

+            );

+            return result;

+          },

+          error => {

+            this.showHasNotBeenUpdated(error);

+          }

+        );

+    });

+  }

+

+  deleteVersion(){

+    let deleteDialog = this.dialog.open(AlertModalComponent, {

+      width: '250px',

+      data: { type: 'confirmation', message: 'Are you sure you want to delete version ' + this.ptd.currentVersionName }

+    });

+

+    deleteDialog.afterClosed().subscribe(

+      result => {

+        if(result){

+          this.inProgress = true;

+          if(this.ptd.bpmnInstances.length == 1){

+            this.testDefinition.delete(this.ptd._id).subscribe(

+              result => {

+                this.childEvent.emit();

+              }

+            )

+          }else{

+            this.ptd.removeBpmnInstance(this.ptd.currentVersionName);

+            this.update().then(

+              res => {

+                this.inProgress = false;

+              }

+            );

+          }

+        }

+      }

+    )

+  }

+

+  // Deploys Test Definition version - Triggerd by "Deploy" button

+  deploy(versionName?) {

+    this.inProgress = true;

+    //console.log(this.ptd)

+    this.testDefinition.deploy(this.ptd, versionName)

+      .subscribe(result => {

+        

+        this.handleResponse(result);

+        this.inProgress = false;

+        if (result['statusCode'] == 200) {

+          this.snack.openFromComponent(AlertSnackbarComponent, {

+            duration: 1500,

+            data: {

+              message: 'Test Definition Deployed!'

+            }

+          });

+          this.ptd.currentInstance.isDeployed = true;

+        } else {

+          this.dialog.open(AlertModalComponent, {

+            width: '250px',

+            data: {

+              type: 'Alert',

+              message: JSON.stringify(result)

+            }

+          });

+        }

+      },

+        err => {

+          this.inProgress = false;

+        }

+

+      );

+  }

+

+  create(data){

+    return new Promise((resolve, reject) => {

+      this.testDefinition.create(data)

+        .subscribe(

+          result => {

+            this.uploadResources(result).then(

+              res => {

+                resolve(res);

+              }

+            );

+          },

+          error => {

+            this.dialog.open(AlertModalComponent, {

+              width: '250px',

+              data: {

+                type: 'Alert',

+                message: JSON.stringify(error)

+              }

+            });

+            reject(error);

+          }

+        );

+    });

+  }

+

+  newVersion(processDefinitionKey){

+    this.hasBeenSaved = false;

+    this.isNew = true;

+    this.ptd.reset();

+    this.ptd.switchVersion();

+    this.ptd.setProcessDefinitionKey(processDefinitionKey);

+  }

+

+  checkProcessDefinitionKey() {

+    this.pStatus = 'loading';

+    this.testDefinition.check(this.ptd.getProcessDefinitionKey()).subscribe(result => {

+      console.log(result);

+      if (result['statusCode'] == 200) {

+        this.pStatus = 'unique';

+      } else {

+        this.pStatus = 'notUnique';

+      }

+

+      this.ptd.bpmnInstances = this.ptd.bpmnInstances.filter((e, i) => {

+        return i == 0;

+      })

+      

+      // this.ptd.bpmnInstances.forEach((elem, val) => {

+      //   if(val > 0){

+      //     this.ptd.bpmnInstances.splice(val, 1);

+      //   }

+      // })

+

+      //New Code

+      if(result['body'] && result['body'][0]){

+        //when changing bpmn dont

+        //if(this.ptd.currentInstance.isDeployed){

+          let res = result['body'][0];

+          this.existingTd = true;

+          this.ptd.setId(res._id);

+          this.ptd.setName(res.testName);

+          this.ptd.setDescription(res.testDescription);

+          this.ptd.setGroupId(res.groupId);

+          this.ptd.setVersion(res.bpmnInstances.length + 1);

+          //this.ptd.bpmnInstances = [];

+

+          for(let i = 0; i < res.bpmnInstances.length; i++){

+            this.ptd.addBpmnInstance(res.bpmnInstances[i]);

+          }

+

+          

+          //this.ptd.addBpmnInstance (res.bpmnInstances);

+        //}

+      }else{

+        this.existingTd = false;

+        this.ptd.setId(null);

+        this.ptd.setName('');

+        this.ptd.setDescription('');

+        this.ptd.setGroupId('');

+        this.ptd.setVersion(1);

+      }

+

+      if(!this.ptd.currentInstance.version){

+        this.ptd.setNewVersion();

+      }

+

+    });

+  }

+

+  validateFile() {

+

+    this.isUploading = true

+    this.fetchFileContents(val => {

+      //

+      this.ptd.currentInstance.bpmnXml = val;

+      if (!this.ptd.currentInstance.bpmnXml) {

+        this.isUploading = false;

+        this.dialog.open(AlertModalComponent, {

+          width: '250px',

+          data: {

+            type: 'Alert',

+            message: 'File was not selected. Please try again.'

+          }

+        });

+        return null;

+      }

+      

+      this.testDefinition.validate(this.ptd.getAll())

+        .subscribe(

+          result => {

+            this.handleResponse(result);

+            //

+            this.isUploading = false;

+            this.ptd.currentInstance.bpmnHasChanged = true;

+            this.loadDiagram();

+          },

+          err => {

+            this.dialog.open(AlertModalComponent, {

+              width: '250px',

+              data: {

+                type: 'Alert',

+                message: 'Something went wrong. Please try again'

+              }

+            });

+            this.isUploading = false;

+          }

+        );

+    });

+

+

+  }

+

+  showHasNotBeenSaved(){

+    this.dialog.open(AlertModalComponent, {

+      width: '250px',

+      data: {

+        type: 'Alert',

+        message: 'There was a problem with saving the test definition.'

+      }

+    });

+    this.inProgress = false;

+  }

+

+  showHasBeenSaved(){

+    this.snack.openFromComponent(AlertSnackbarComponent, {

+      duration: 1500,

+      data: {

+        message: 'Test Definition Saved!'

+      }

+    });

+    //this.switchVersion();

+    this.ptd.switchVersion();

+    this.hasBeenSaved = true;

+    this.saved = true;

+    this.form.form.markAsPristine();

+    this.inProgress = false;

+  }

+

+  showHasBeenUpdated(){

+    this.snack.openFromComponent(AlertSnackbarComponent, {

+      duration: 1500,

+      data: {

+        message: 'Test Definition Updated!'

+      }

+    });

+    //this.switchVersion();

+    this.ptd.switchVersion(this.ptd.currentInstance.version);

+    this.saved = true;

+    this.form.form.markAsPristine();

+    this.ptd.currentInstance.bpmnHasChanged = false;

+    this.inProgress = false;

+  }

+

+  showHasNotBeenUpdated(error = null){

+    this.dialog.open(AlertModalComponent, {

+      width: '250px',

+      data: {

+        type: 'Alert',

+        message: JSON.stringify(error)

+      }

+    });

+    this.inProgress = false;

+  }

+

+  setTestDefinition(data = null){

+    //new

+    if(data){

+      

+      this.ptd.setAll(data);

+    }else{

+      this.ptd.setAll(JSON.parse(JSON.stringify(this.formData)));

+    }

+

+    this.switchVersion();

+

+    //console.log(this.ptd);

+    

+  }

+

+  clearQueue(){

+    this.uploader.clearQueue();

+    if(this.scripts){

+      this.scripts.nativeElement.value = null;

+    }

+  }

+

+  switchVersion(versionName = null){

+    this.ptd.switchVersion(versionName);

+    this.checkTestDataTemplate();

+

+    this.clearQueue();

+    this.bpmnFileInput.nativeElement.value = null;

+

+    //Get bpmn file contents

+    this.fileTransfer.get(this.ptd.currentInstance.bpmnFileId).subscribe(

+      result => {

+        result = new Buffer(result as Buffer);

+        this.ptd.currentInstance.bpmnXml = result.toString();

+        this.loadDiagram();

+      }

+    );

+

+    //get info on resource file

+    if(this.ptd.currentInstance.resourceFileId){

+      this.fileService.get(this.ptd.currentInstance.resourceFileId).subscribe(

+        result => {

+          this.ptd.currentInstance.resourceFileName = result['filename'];

+        }

+      )

+    }

+

+    if(this.ptd.currentInstance.testHeads){

+      this.ptd.currentInstance.dataTestHeads = [];

+      this.ptd.currentInstance.testHeads.forEach((elem, val) => {

+        //Find test head info

+        const e = elem;

+        this.testHead.get(e.testHeadId).subscribe(

+          result => {

+            this.ptd.currentInstance.dataTestHeads.push({

+              testHeadId: e.testHeadId,

+              bpmnVthTaskId: e.bpmnVthTaskId,

+              testHead: JSON.parse(JSON.stringify(result))

+            });

+          },

+          err => {

+            this.ptd.currentInstance.dataTestHeads.push({

+              testHeadId: e.testHeadId,

+              bpmnVthTaskId: e.bpmnVthTaskId,

+              testHead: { _id: e.testHeadId, testHeadName: 'No Access' }

+            });

+          }

+        );

+      });

+    }

+  }

+

+  gatherTestDefinition(bpmnFile = null) {

+

+    if(bpmnFile){

+      this.ptd.currentInstance.bpmnFileId = bpmnFile._id;

+    }

+

+    this.ptd.currentInstance.testHeads = [];

+    this.ptd.currentInstance.dataTestHeads.forEach((elem, val) => {

+      this.ptd.currentInstance.testHeads.push({

+        testHeadId: elem.testHead._id,

+        bpmnVthTaskId: elem.bpmnVthTaskId

+      });

+    });

+

+    return this.ptd.getAll();

+  

+  }

+

+  saveDeploy() {

+    let version = JSON.parse(JSON.stringify(this.ptd.currentInstance.version));

+    console.log(version)

+    this.save().then(x => {

+      this.deploy(version);

+    });

+  }

+

+  updateDeploy() {

+    let version = JSON.parse(JSON.stringify(this.ptd.currentInstance.version));

+    this.update().then(x => {

+      this.deploy(version);

+    });

+  }S

+

+  handleResponse(result) {

+    this.successUpload = true;

+    this.processDefinitionKey = false;

+    //this.validateResponse = result;

+    if (result['body']['errors']) {

+

+

+      if (result['body']['errors']['processDefinitionKey']) {

+        this.openProcessDefinitionKeyModal();

+        this.pStatus = 'notUnique';

+        this.ptd.setProcessDefinitionKey(result['body'].errors.processDefinitionKey.key)

+        //this.td.processDefinitionKey = result['body']['errors']['processDefinitionKey']['key'];

+        this.processDefinitionKey = true;

+      }

+      if (result['body']['errors']['notFound']) {

+        this.dialog.open(AlertModalComponent, {

+          width: '250px',

+          data: { type: 'alert', message: result['body']['errors']['notFound']['error'] }

+        });

+        this.successUpload = false;

+      }

+      if (result['body']['errors']['startEvent']) {

+        this.dialog.open(AlertModalComponent, {

+          width: '250px',

+          data: { type: 'alert', message: result['body']['errors']['startEvent']['error'] }

+        });

+        this.successUpload = false;

+      }

+      if (result['body']['errors']['required']) {

+        this.dialog.open(AlertModalComponent, {

+          width: '250px',

+          data: { type: 'alert', message: result['body']['errors']['required']['error'] }

+        });

+        this.successUpload = false;

+      }

+      if (result['body']['errors']['permissions']) {

+        let mess = '';

+        result['body']['errors']['permissions'].forEach(elem => {

+          mess += elem.error + '\n';

+        })

+        this.dialog.open(AlertModalComponent, {

+          width: '250px',

+          data: { type: 'alert', message: mess }

+        });

+        this.successUpload = false;

+      }

+

+    }else{

+      this.markAsDirty();

+    }

+    // Update list of test heads

+    if (result['body']['bpmnVthTaskIds']) {

+      this.ptd.currentInstance.dataTestHeads = result['body'].bpmnVthTaskIds;

+      this.ptd.currentInstance.testHeads = [];

+      //this.definitionInstance.testHeads = result['body']['bpmnVthTaskIds'];

+    }

+

+    //Update plfos list

+    if(result['body']['bpmnPfloTaskIds']){

+      this.ptd.currentInstance.pflos = result['body'].bpmnPfloTaskIds;

+    }

+

+    if (result['body']['processDefinitionKey']) {

+      this.ptd.setProcessDefinitionKey(result['body'].processDefinitionKey);

+      //this.td.processDefinitionKey = result['body']['processDefinitionKey'];

+      this.checkProcessDefinitionKey()

+    }

+  }

+

+  markAsDirty() {

+    this.form.control.markAsDirty();

+  }

+

+  //returns promise for file object 

+  saveBpmnFile() {

+    return new Promise((resolve, reject) => {

+

+      //check for bpmnXml

+      if (!this.ptd.currentInstance.bpmnXml) {

+        this.dialog.open(AlertModalComponent, {

+          width: '250px',

+          data: {

+            type: 'Alert',

+            message: 'No File found. Please select a file to upload'

+          }

+        });

+        reject();

+      }

+

+      if(this.ptd.currentInstance.bpmnHasChanged){

+        // Upload

+        console.log('validate save call')

+        this.testDefinition.validateSave(this.ptd).subscribe(

+          result => {

+            resolve(JSON.parse(result.toString())[0]);

+          }

+        );

+      }else{

+        //bpmn has not changed, so did not save it.

+        resolve(null);

+      }

+    });

+  }

+

+  saveVersion(data){

+    return new Promise((resolve, reject) => {

+

+      let newBpmnInsance = JSON.parse(JSON.stringify(data.bpmnInstances[0]));

+      delete data.bpmnInstances;

+      data['$push'] = {

+        bpmnInstances: newBpmnInsance

+      }

+

+      console.log(data)

+

+      this.testDefinition.patch(data).subscribe(

+        result => {

+          this.uploadResources(result).then(

+            res => {

+              resolve(res);

+            }

+          )

+        },

+        err => {

+          reject(err);

+        }

+      )

+    });

+  }

+

+  uploadResources(td){

+    return new Promise((resolve, reject) => {

+      if(this.uploader.queue.length > 0){

+        //console.log('has file');

+        this.uploader.uploadAll();

+        this.uploader.onCompleteItem = (item: FileItem, response: string, status: Number, headers: ParsedResponseHeaders) => {

+          this.scriptFiles.push(JSON.parse(response)[0]);

+          //console.log('in file')

+        }

+        this.uploader.onCompleteAll = () => {

+          //console.log('complete')

+          let scriptFilesId = [];

+          for (let i = 0; i < this.scriptFiles.length; i++) {

+            scriptFilesId.push(this.scriptFiles[i]['_id']);

+          }

+          td['bpmnInstances'][this.ptd.currentVersion]['resourceFileId'] = scriptFilesId[0];

+          //console.log(td);

+          this.testDefinition.patch(td).subscribe(

+            res => {

+              //console.log(res);

+              resolve(res);

+            },

+            err => {

+              reject(err);

+            }

+          );

+        }

+      }else{

+        resolve(td);

+      }

+    });

+  }

+

+  checkTestDataTemplate() {

+    if (this.ptd.currentInstance.testDataTemplate == null || this.ptd.currentInstance.testDataTemplate == '') {

+      delete this.ptd.currentInstance.testDataTemplate;

+    }

+    // if (this.definitionInstance.testDataTemplate == null || this.definitionInstance.testDataTemplate == '') {

+    //   delete this.definitionInstance.testDataTemplate;

+    // }

+  }

+

+  async loadDiagram() {

+    if (this.ptd.currentInstance.bpmnXml) {

+      //render xml and display

+      this.viewer.setBpmnXml(this.ptd.currentInstance.bpmnXml);

+      // if (!this.viewer) {

+      //   this.viewer = new Modeler({

+      //     container: this.canvas.nativeElement

+      //   });

+      // }

+

+      // this.viewer.importXML(this.ptd.currentInstance.bpmnXml, (err) => {

+      //   if (!err) {

+      //     this.viewer.get('canvas').zoom('fit-viewport');

+      //   } else {

+      //     //

+      //   }

+      // });

+

+    }

+  }

+

+  enlargeBpmn(){

+    this.dialog.open(ViewWorkflowModalComponent, {

+      data: {

+        xml: this.ptd.currentInstance.bpmnXml

+      },

+      width: '100%',

+      height: '100%'

+    })

+  }

+

+  fetchFileContents(callback) {

+    var val = "x";

+    var fileToLoad = (document.getElementById('file'))['files'][0];

+    var fileReader = new FileReader();

+    if (!fileToLoad) {

+      return null;

+    }

+    fileReader.onload = function (event) {

+      //

+      val = event.target['result'] as string;

+

+      //

+      callback(val);

+    }

+    fileReader.readAsText(fileToLoad);

+  }

+

+  openProcessDefinitionKeyModal() {

+    const dialogRef = this.dialog.open(AlertModalComponent, {

+      width: '250px',

+      data: { type: 'warning', message: 'You cannot use this process definition key. Please change it.' }

+    });

+  }

+

+  checkVersionUnique(){

+    let exists = false;

+    this.ptd.bpmnInstances.forEach(elem => {

+      if(elem != this.ptd.currentInstance && elem.version == this.ptd.currentInstance.version){

+        exists = true;

+      }

+    });

+

+    if(exists){

+      this.form.controls['version'].setErrors({error: 'Version Already Exists'});

+    }else{

+      this.form.controls['version'].setErrors(null);

+    }

+  }

+

+}
\ 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
index 0000000..595b3d1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.module.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { CreateTestFormComponent } from './create-test-form.component';

+

+describe('CreateTestFormComponent', () => {

+  let component: CreateTestFormComponent;

+  let fixture: ComponentFixture<CreateTestFormComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ CreateTestFormComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(CreateTestFormComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..7466c81
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-form/create-test-form.module.ts
@@ -0,0 +1,77 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { CreateTestFormComponent } from './create-test-form.component';

+import { FormsModule, ReactiveFormsModule } from '@angular/forms';

+import { MatButtonModule, MatIconModule, MatTooltipModule, MatInputModule, MatBadgeModule, MatOptionModule, MatSelectModule,

+    MatSnackBarModule, 

+    MatSlideToggleModule,

+    MatListModule} from '@angular/material';

+import { MatProgressButtonsModule} from 'mat-progress-buttons';

+import { PageHeaderModule } from '../page-header/page-header.module';

+import { PerfectScrollbarModule, PERFECT_SCROLLBAR_CONFIG, PerfectScrollbarConfigInterface } from 'ngx-perfect-scrollbar';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { SelectStrategyModalModule } from '../select-strategy-modal/select-strategy-modal.module';

+import { SelectTestHeadModalModule } from '../select-test-head-modal/select-test-head-modal.module';

+import { CodemirrorModule } from 'ng2-codemirror';

+import { MatExpansionModule} from '@angular/material/expansion';

+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

+import { AlertModalModule } from '../alert-modal/alert-modal.module';

+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';

+import { FileUploadModule } from 'ng2-file-upload';

+import { Bpmn } from 'app/shared/models/bpmn.model';

+

+const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {

+  suppressScrollY: true

+};

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FilterPipeModule,

+    FormsModule,

+    ReactiveFormsModule,

+    PageHeaderModule,

+    PerfectScrollbarModule,

+    MatButtonModule,

+    SelectTestHeadModalModule,

+    SelectStrategyModalModule,

+    MatIconModule,

+    CodemirrorModule,

+    MatTooltipModule,

+    MatInputModule,

+    MatExpansionModule,

+    MatProgressSpinnerModule,

+    MatBadgeModule,

+    AlertModalModule,

+    MatSelectModule,

+    MatOptionModule,

+    AlertSnackbarModule,

+    MatSnackBarModule,

+    FileUploadModule,

+    MatSlideToggleModule,

+    MatProgressButtonsModule,

+    MatListModule

+  ],

+  declarations: [CreateTestFormComponent],

+  exports: [CreateTestFormComponent],

+  providers: [

+    { provide: PERFECT_SCROLLBAR_CONFIG, useValue: DEFAULT_PERFECT_SCROLLBAR_CONFIG }

+  ]

+})

+export class CreateTestFormModule { }

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
index 0000000..d73e48a
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-form/definition-instance.class.ts
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+export class DefinitionInstance {

+

+    public bpmnFileId: String;

+    public bpmnXml: any;

+    public resourceFileId: String;

+    public resourceFileName: String;

+    public isDeployed: Boolean;

+    public testHeads: TestHead[];

+    public dataTestHeads: DataTestHead[];

+    public testDataTemplate: String;

+    public testDataTemplateJSON: any;

+    public version: String;

+    public bpmnHasChanged: Boolean;

+    public pflos: Pflo[];

+

+    constructor(){

+        this.testDataTemplate = '';

+        this.version = '';

+        this.testHeads = [];

+        this.dataTestHeads = [];

+        this.pflos = [];

+        this.isDeployed = false;

+        this.bpmnFileId = null;

+        this.resourceFileName = null;

+        this.bpmnXml = null;

+        this.resourceFileId = null;

+        this.bpmnHasChanged = false;

+    }

+

+}

+

+interface TestHead {

+    bpmnVthTaskId: String;

+    testHeadId: String;

+}

+

+interface DataTestHead extends TestHead {

+    testHead: any;

+}

+

+interface Pflo {

+    bpmnPfloTaskId: String;

+    label: String;

+}
\ 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
index 0000000..0303d13
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-form/test-definition.class.ts
@@ -0,0 +1,222 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { DefinitionInstance } from "./definition-instance.class";

+import { element } from "@angular/core/src/render3/instructions";

+

+export class TestDefinition {

+

+    public _id: String;

+    public testName: String;

+    public testDescription: String;

+    public groupId: String;

+    public processDefinitionKey: String;

+

+    public bpmnInstances: DefinitionInstance[];

+

+    public currentVersion; // int Array index of the bpmnInstances

+    public currentVersionName;

+    public currentInstance: DefinitionInstance;

+

+    constructor(testDefinition: TestDefinition = null){

+        if(testDefinition){

+            this.setAll(testDefinition);

+        }

+    }

+

+

+    reset(){

+        this._id = '';

+        this.testName = '';

+        this.testDescription = '';

+        this.groupId = '';

+        this.processDefinitionKey = '';

+        this.bpmnInstances = [

+            this.newInstance() as DefinitionInstance

+        ];

+        this.currentInstance = this.bpmnInstances[0];

+        this.currentVersion = 0;

+    }

+

+    getAll(){

+        return {

+            _id: this._id,

+            testName: this.testName,

+            testDescription: this.testDescription,

+            processDefinitionKey: this.processDefinitionKey,

+            bpmnInstances: this.bpmnInstances,

+            currentVersion: this.currentVersion

+        };

+    }

+

+    switchVersion(version: String = null){

+        

+        if(version){

+            //find the version

+            this.bpmnInstances.forEach((elem, val) => {

+                if(elem['version'] == version){

+                    this.currentVersion = val;

+                    this.currentInstance = this.bpmnInstances[val];

+                    this.currentVersionName = this.currentInstance.version;

+                }

+            });

+        }else{

+            //get latest version

+            this.currentVersion = this.bpmnInstances.length - 1;

+            this.currentInstance = this.bpmnInstances[this.currentVersion];

+            this.currentVersionName = this.currentInstance.version;

+        }

+    }

+

+    getVersionKey(){

+        return this.currentVersion;

+    }

+

+    //Setter Methods

+

+    setAll(td){

+        this._id = td._id;

+        this.testName = td.testName;

+        this.testDescription = td.testDescription;

+        this.groupId = td.groupId;

+        this.processDefinitionKey = td.processDefinitionKey;

+        this.setBpmnInstances(td.bpmnInstances);

+

+        this.bpmnInstances.forEach((elem, val) => {

+            if(!elem.dataTestHeads)

+                this.bpmnInstances[val].dataTestHeads = [];

+        })

+    }

+

+    setId(id: String){

+        this._id = id;

+    }

+

+    setName(testName: String){

+        this.testName = testName;

+    }

+

+    setDescription(testDescription: String){

+        this.testDescription = testDescription;

+    }

+

+    setGroupId(groupId: String){

+        this.groupId = groupId;

+    }

+

+    setProcessDefinitionKey(processDefinitionKey: String){

+        this.processDefinitionKey = processDefinitionKey;

+    }

+

+    setBpmnInstances(instances: DefinitionInstance[] = []){

+        // this.bpmnInstances = [];

+        // for(let i = instances.length - 1; i >= 0; i--){

+        //     this.bpmnInstances.push(instances[i]);

+        // }

+        this.bpmnInstances = instances;

+    }

+

+    setNewVersion(newVersion: number = null){

+        if(newVersion == null){

+            newVersion = this.bpmnInstances.length;

+        }

+        if(this.setVersion(newVersion) == -1){

+            this.setNewVersion(++newVersion);

+        }

+        return newVersion;

+    }

+

+    setVersion(version){

+        

+        this.bpmnInstances.forEach((elem, val) => {

+            

+            

+            if(elem.version == version && this.currentVersion != val ){

+                return -1;

+            }

+        });

+        this.currentInstance.version = version;

+        return version;

+    }

+

+    addBpmnInstance(instance = null){

+        

+        if(!instance){

+           instance = this.newInstance();

+        }

+        let alreadyIn = false;

+        this.bpmnInstances.forEach((elem, val) => {

+            if(elem.version == instance.version && val != 0){

+                alreadyIn = true;

+            }

+        });

+        if(!alreadyIn){

+            this.bpmnInstances.push(instance);

+            this.setNewVersion()

+        }

+        

+    }

+

+    removeBpmnInstance(version){

+        this.bpmnInstances.forEach((elem, val) =>{

+            if(elem['version'] == version){

+                this.bpmnInstances.splice(val, 1);

+            }

+        });

+    }

+

+    //Getter Methods

+

+    getId(){

+        return this._id;

+    }

+

+    getName(){

+        return this.testName;

+    }

+

+    getDescription(){

+        return this.testDescription;

+    }

+

+    getGroupId(){

+        return this.groupId;

+    }

+

+    getProcessDefinitionKey(){

+        return this.processDefinitionKey;

+    }

+

+    getBpmnInstances(version: String = null){

+        if(!version)

+            return this.bpmnInstances;

+        

+        this.bpmnInstances.forEach((elem, val) => {

+            if(elem['version'] == version){

+                return elem;

+            }

+        });

+    }

+

+    newInstance() {

+        return new DefinitionInstance();

+    }

+

+

+

+

+

+}
\ 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
index 0000000..8807f2d
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.pug
@@ -0,0 +1,78 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+form(#testHeadForm="ngForm", style="width:100%")

+  .row

+    .col-sm-6

+      mat-form-field(*ngIf="vth._id")

+        input(matInput, type="text", name="_id", placeholder="Test Head ID", [ngModel]='vth._id', disabled)

+

+      mat-form-field

+        input(matInput, type="text", name="test_head_name", placeholder="Name", [(ngModel)]="vth.testHeadName", required)

+

+      mat-form-field

+        input(matInput, type="text", name="test_head_hostname", placeholder="Hostname", [(ngModel)]="vth.hostname")

+

+      mat-form-field

+        input(matInput, type="text", name="test_head_urlPath", placeholder="Resource Path", [(ngModel)]="vth.resourcePath")

+

+    .col-sm-6

+      mat-form-field

+        input(matInput, name="description", placeholder="Description", [(ngModel)]="vth.testHeadDescription", required)

+

+      mat-form-field

+        input(matInput, type="text", name="test_head_port", placeholder="Port", [(ngModel)]="vth.port")

+

+      .row

+          .col-sm-4

+             mat-checkbox(name="test_head_authorization_enabled", (change)="markAsDirty()", [(ngModel)]="vth.authorizationEnabled") Authorization

+

+          .col-sm-3

+              mat-form-field

+                  input(matInput, type="text", name="test_head_authorization_type", placeholder="Type (ex: ApiKey)", [(ngModel)]="vth.authorizationType")

+

+          .col-sm-5

+              mat-form-field

+                  input(matInput, type="text", autocomplete="off", name="test_head_authorization_credential", placeholder="Password", [(ngModel)]="vth.authorizationCredential")

+

+

+

+          //- mat-form-field

+      //-   mat-select((selectionChange)="markAsDirty()", name="ns", placeholder="User Group", [(value)]="vth.groupId", required)

+      //-     mat-option(*ngFor="let group of groups", value="{{group._id}}") {{ group.groupName }}

+

+    .col-12

+      h5.text-muted vthInputTemplate.yaml

+      input( type="file", id="file", (change)="saveFileContents()")

+      div(style="border: 1px solid lightgrey; font-size: 16px !important")

+        codemirror([config]="codeConfig", [(ngModel)]='vth.vthInputTemplate', name="vthInputTemplate")

+

+  //- .row.mt-3

+  //-   .col

+  //-     h5.text-muted vthOutputTemplate.yaml

+  //-     div(style="border: 1px solid lightgrey; font-size: 16px !important")

+  //-       codemirror([config]="codeConfig", [(ngModel)]='vth.vthOutputTemplate', name="vthOutputTemplate")

+

+

+  .row(style="height:30px")

+  .row.form-buttons

+    .col-12

+      .pull-left

+        .mr-3(mat-button, *ngIf="testHeadForm.form.valid && !testHeadForm.form.dirty && options.goal == 'edit'") saved

+          mat-icon(style="color:green") check

+      .pull-right

+        button.mr-3(mat-raised-button, color="primary", (click)='create()', *ngIf="options.goal == 'create'", [disabled]="!testHeadForm.form.valid") Create

+        button.mr-3(mat-raised-button, color="accent", (click)='update()', *ngIf="options.goal == 'edit'", [disabled]="!testHeadForm.form.valid || !testHeadForm.form.dirty") Update

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
index 0000000..8d6ae72
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.scss
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// .ng-valid[required], .ng-valid.required  {

+//     border-left: 3px solid #42A948; /* green */

+// }

+

+// .ng-invalid:not(form)  {

+//     border-left: 3px solid #a94442; /* red */

+// }

+

+// .ng-dirty:not(form) {

+//     border-left: 3px solid #045C87; /* blue */

+// }

+

+mat-form-field {

+    width: 100%;

+}

+

+.CodeMirror-scroll {

+    height: 200px

+}
\ 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
index 0000000..efb2b4a
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { CreateTestHeadFormComponent } from './create-test-head-form.component';

+

+describe('CreateTestHeadFormComponent', () => {

+  let component: CreateTestHeadFormComponent;

+  let fixture: ComponentFixture<CreateTestHeadFormComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ CreateTestHeadFormComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(CreateTestHeadFormComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..4e0f459
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.component.ts
@@ -0,0 +1,170 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, Output, EventEmitter, ViewChild } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { ListService } from '../../services/list.service';

+import { TestHeadService } from '../../services/test-head.service';

+import { GroupService } from '../../services/group.service';

+import 'codemirror/mode/yaml/yaml.js';

+import { MatSnackBar, MatDialog, MatDialogRef } from '@angular/material';

+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';

+import { AlertModalComponent } from '../alert-modal/alert-modal.component';

+

+

+@Component({

+  selector: 'app-create-test-head-form',

+  templateUrl: './create-test-head-form.component.pug',

+  styleUrls: ['./create-test-head-form.component.scss']

+})

+export class CreateTestHeadFormComponent implements OnInit {

+  yaml;

+

+  private hasPrevCredential;

+

+  public codeConfig = {

+    mode: "yaml",

+    theme: "eclipse",

+    lineNumbers: true

+  };

+

+  @Input() public formData;

+  @Input() public options;

+

+  @Output() public childEvent = new EventEmitter();

+

+  //Virtual Test Head Type Options

+  types = [

+    'Proxy',

+    'Regular',

+    'Script',

+    'Adapter'

+  ]

+

+  //Implementation Language Options

+  langs = [

+    'Java',

+    'Python',

+    'JavaScript/NodeJS'

+  ]

+

+  public vth;

+  public groups;

+

+  @ViewChild('testHeadForm') form: any;

+

+  constructor(public dialogRef: MatDialogRef<CreateTestHeadFormComponent>, private http: HttpClient, private list: ListService, private dialog: MatDialog, private snack: MatSnackBar, private testHead: TestHeadService, private group: GroupService) { }

+

+  ngOnInit() {

+    this.setNew();

+    if(this.formData){

+      this.vth = Object.assign({}, this.formData);

+      if(!this.vth.authorizationCredential){

+          this.vth.authorizationCredential = "";

+          this.hasPrevCredential = false;

+      }

+      else{

+          this.hasPrevCredential = true

+      }

+    }

+  }

+

+  markAsDirty(){

+    this.form.control.markAsDirty();

+  }

+

+  create(){

+

+    this.testHead.create(this.vth)

+    .subscribe((vth) => {

+      //this.list.addElement('vth', vth);

+      this.clear(this.form);

+      this.snack.openFromComponent(AlertSnackbarComponent, {

+        duration: 1500,

+        data: {

+          message:'Test Head Created'

+        }

+      });

+      this.dialogRef.close();

+      //this.dialog.closeAll();

+    }, err => {

+      this.dialog.open(AlertModalComponent, {

+        data: {

+          type: 'alert',

+          message: JSON.stringify(err)

+        },

+        width: '450px'

+      })

+    });

+

+  }

+  //grab file

+  saveFileContents(){

+    this.getFileContents(val => {

+      this.vth.vthInputTemplate = val;

+    });

+  }

+

+  getFileContents(callback) {

+    var val = "x";

+    var fileToLoad = (document.getElementById('file'))['files'][0];

+    var fileReader = new FileReader();

+    if (!fileToLoad) {

+      return null;

+    }

+    fileReader.onload = function (event) {

+      //

+      val = event.target['result'];

+

+      //

+      callback(val);

+    }

+    fileReader.readAsText(fileToLoad);

+  }

+

+  update(){

+    if(!this.hasPrevCredential && this.vth.authorizationCredential == ""){

+          delete this.vth.authorizationCredential;

+    }

+    this.testHead.patch(this.vth)

+    .subscribe((vth) => {

+      // this.list.updateElement('vth', '_id', vth['_id'], vth);

+      this.childEvent.emit();

+        this.snack.openFromComponent(AlertSnackbarComponent, {

+            duration: 1500,

+            data: {

+                message:'Test Head Updated'

+            }

+        });

+        this.dialogRef.close();

+    });

+  }

+

+  clear(form){

+    this.setNew();

+    if(form){

+      form.reset();

+    }

+    this.childEvent.emit();

+  }

+

+  setNew(){

+    this.vth = {};

+    this.vth.vthInputTemplate = '';

+

+    //this.vth.vthOutputTemplate = '';

+  }

+}

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
index 0000000..b039050
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { CreateTestHeadFormModule } from './create-test-head-form.module';

+

+describe('CreateTestHeadFormModule', () => {

+  let createTestHeadFormModule: CreateTestHeadFormModule;

+

+  beforeEach(() => {

+    createTestHeadFormModule = new CreateTestHeadFormModule();

+  });

+

+  it('should create an instance', () => {

+    expect(createTestHeadFormModule).toBeTruthy();

+  });

+});

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
index 0000000..14b83e1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-head-form/create-test-head-form.module.ts
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { CreateTestHeadFormComponent } from './create-test-head-form.component';

+import { FormsModule } from '@angular/forms';

+import {

+    MatButtonModule,

+    MatInputModule,

+    MatSelectModule,

+    MatOptionModule,

+    MatSnackBarModule,

+    MatIconModule,

+    MatDialogModule,

+    MatSlideToggleModule, MatCheckboxModule

+} from '@angular/material';

+import { CodemirrorModule } from 'ng2-codemirror';

+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    CodemirrorModule,

+    FormsModule,

+    MatButtonModule,

+    MatInputModule,

+    MatSelectModule,

+    MatOptionModule,

+    MatSnackBarModule,

+    AlertSnackbarModule,

+    MatIconModule,

+    MatDialogModule,

+    MatSlideToggleModule,

+    MatCheckboxModule

+  ],

+  declarations: [CreateTestHeadFormComponent],

+  exports: [CreateTestHeadFormComponent ]

+})

+export class CreateTestHeadFormModule { }

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
index 0000000..8739065
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.pug
@@ -0,0 +1,139 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+.row(style="margin-left: 0px")

+  .col

+    button.mr-2.mt-2(mat-raised-button, color="primary", (click)="getDefinition()") Select Definition

+    label() Selected Test Defintion: {{ selectedDefinition.testName || "None Selected" }}

+    div.mt-4(*ngIf="selectedDefinition.testName")

+      .col-md-12.mb-4

+        .row

+          mat-form-field.mr-3

+            input(matInput, [(ngModel)]="testInstance.testInstanceName", placeholder="Instance Name", required)

+            mat-error Required

+          mat-form-field

+            input(matInput, [(ngModel)]="testInstance.testInstanceDescription", placeholder="Description") 

+        .row

+          Label() Select BPMN Version

+        .row 

+          .col-md-4

+            mat-select.mr-2([(value)]="selectedBpmn", [disabled]="testInstance.useLatestTestDefinition || editMode", (selectionChange)="changeBpmn()") 

+              mat-option( *ngFor="let bpmn of selectedDefinition.bpmnInstances | filterNonDeployed: myFilter", [value]="bpmn") {{bpmn.version}}

+

+          .col-md-4

+            mat-slide-toggle(*ngIf='!editMode', [(ngModel)]="testInstance.useLatestTestDefinition", (change)="useLatest()") Use latest

+            mat-slide-toggle.ml-2(color="primary", [(ngModel)]="testInstance.simulationMode", (change)="simulationMode()") Simulation Mode

+          

+      mat-accordion

+        mat-expansion-panel([expanded]="false")

+          mat-slide-toggle((change)="toggleYaml()", [checked]="displayYAML") Display Yaml Input

+          mat-expansion-panel-header Test Input

+          div(*ngIf='testInstance.testDataJSON || selectedBpmn.testDataTemplateJSON')

+            app-form-generator(*ngIf="!displayYAML", [JSONData] = 'testInstance.testDataJSON || selectedBpmn.testDataTemplateJSON', [taskId]= '', (childEvent)="saveTestDataOptions($event)" )

+          codemirror(*ngIf="displayYAML", [config]="codeConfig", [(ngModel)] = "testInstance['testData']")

+      

+      //- If Not in simulation mode, display vth input fields

+      div(*ngIf="!testInstance.simulationMode")

+        mat-slide-toggle.mt-4.mb-2((change) = "testHeadYaml()") Display Yaml (All VTHs)

+        mat-accordion(*ngFor = 'let testHead of selectedBpmn.testHeads; let i = index')

+          mat-expansion-panel(*ngIf="editMode || (testHead.testHeadId.testHeadName && testHead.testHeadId.testHeadName.toLowerCase() != 'robot')",[expanded]='false')

+            mat-expansion-panel-header {{ testHead.testHeadId.testHeadName || testInstance.vthInput[testHead.bpmnVthTaskId + "testHeadName"] }} ({{testHead.bpmnVthTaskId}})

+            app-form-generator(*ngIf= "!testHeadYAML", [JSONData] = 'testInstance.vthInput[testHead.bpmnVthTaskId] || testHead["testHeadId"]["vthInputTemplateJSON"]', [taskId]="testHead.bpmnVthTaskId",  (childEvent)="saveFormOptions($event)")

+

+            codemirror(*ngIf="testHeadYAML", [config]="codeConfig", [(ngModel)] = "testInstance['vthInputYaml'][testHead.bpmnVthTaskId]")

+      

+          mat-expansion-panel(*ngIf="(testHead.testHeadId.testHeadName && testHead.testHeadId.testHeadName.toLowerCase() == 'robot')", [expanded]='false')

+            mat-expansion-panel-header {{ testHead.testHeadId.testHeadName || testInstance.vthInput[testHead.bpmnVthTaskId + "testHeadName"]}} ({{testHead.bpmnVthTaskId}}) Robot Files

+            mat-panel-title Resources

+            .row

+              .col-md-3

+                //- .mb-2 TESTING GIT TRACKING

+                //-   | Multiple Files 

+                //-   mat-slide-toggle(color="primary", name="isZip", [(ngModel)]="isZip", (change)="uploader.clearQueue()")

+                //-   |  .zip

+                //- div 

+                //-   input(*ngIf="!isZip", type="file", name="scripts", ng2der")FileSelect, [uploader]="uploader", multiple)

+                input(*ngIf="isZip", type="file", name="scripts", ng2FileSelect, [uploader]="uploaders[testHead.bpmnVthTaskId]", accept="application/zip")

+              .col-md-8.ml-2

+                div(*ngIf="uploaders[testHead.bpmnVthTaskId].queue.length > 0")

+                  label Files:

+                  ul.list-group(style="position:relative")

+                    li.list-group-item(*ngFor="let item of uploaders[testHead.bpmnVthTaskId].queue")

+                      | {{ item?.file?.name }}

+                      div.upload-progress([ngStyle]="{'width': item.progress + '%'}")

+                  //button.pull-right(mat-button, (click)="upload()") Upload All

+                  button.pull-right(mat-button, color="primary", (click)="uploaders[testHead.bpmnVthTaskId].clearQueue()") Remove All

+      

+      //- If in simulation mode, show simulated outputs and delays

+      div.mt-4(*ngIf="testInstance.simulationMode && testInstance.simulationVthInput")

+        mat-accordion

+          mat-expansion-panel(*ngFor="let testHead of selectedBpmn.testHeads; let i = index")

+            mat-expansion-panel-header 

+              span(style="color: #2196f3") Simulated 

+              | &nbsp; {{ testHead.testHeadId.testHeadName || testInstance.vthInput[testHead.bpmnVthTaskId + "testHeadName"] }} ({{testHead.bpmnVthTaskId}})

+            codemirror([config]="codeJsonConfig", *ngIf="testInstance.simulationVthInput[testHead.bpmnVthTaskId]", [(ngModel)]="testInstance.simulationVthInput[testHead.bpmnVthTaskId]")

+          //- h5.text-muted testHeadData.yaml

+          //-  div(style="border: 1px solid lightgrey")

+          //-   codemirror([config]="codeConfig", value = "{{ testInstance['testData']}}", [(ngModel)]='testInstance["testData"]')

+      div.mt-4(*ngIf="checkPfloInputLength()")

+        h4 PFLO Inputs

+        mat-accordion

+          mat-expansion-panel(*ngFor="let pflo of selectedBpmn.pflos; let i = index" color="primary")

+            mat-expansion-panel-header {{testInstance.pfloInput[pflo.bpmnPfloTaskId + "pfloName"]}} ({{pflo.bpmnPfloTaskId}})

+            .row

+              .col-md-6()

+                h5 Stop on Failure

+                mat-form-field

+                  mat-select(placeholder="Interrupt On Failure", [(value)]="testInstance.pfloInput[pflo.bpmnPfloTaskId]['interruptOnFailure']", required)

+                    mat-option([value]="false") False

+                    mat-option([value]="true") True

+                h5 Max Number of Failures

+                mat-form-field

+                  input(matInput, type="number", [(ngModel)] = "testInstance.pfloInput[pflo.bpmnPfloTaskId]['maxFailures']")

+              .col-md-6

+                h5 Number of Threads

+                mat-form-field

+                  input(matInput, type="number", [(ngModel)] = "testInstance.pfloInput[pflo.bpmnPfloTaskId]['threadPoolSize']")

+                

+                .dropdown.mt-1(ngbDropdown, autoClose="outside", (openChange)="clearSelectedValues()", placement="left-top")

+                  button(mat-raised-button, [disabled]="editMode",  color="primary", ngbDropdownToggle, (click)="null") Add Instance

+                    i.ml-1.fa.fa-caret-down

+                  .dropdown-menu(ngbDropdownMenu)

+                    h4.mb-2.ml-1(style="font-weight: bold;") Add Instances

+                    input.ml-1(matInput, type='search', placeholder='Search...', color='blue', [(ngModel)]='search.testInstanceName')

+                    div(style="max-height: 300px; overflow-y: scroll")

+                      .px-4.py-3

+                        .mr-2.ml-2(*ngFor="let instance of instances | filterBy:search")

+                          mat-checkbox([(ngModel)]='instance.isSelected') {{instance.testInstanceName}} 

+                    div( style="text-align: center")            

+                      button.primary.mr-1(mat-raised-button, aria-label='Add', color="primary", (click)='addInstancesToPflo(pflo.bpmnPfloTaskId)') Add

+                  

+              h4.mt-2(*ngIf="testInstance.pfloInput[pflo.bpmnPfloTaskId].args.length && !editMode", style="width:100%") Workflows

+                mat-accordion

+                  mat-expansion-panel(*ngFor="let workReq of testInstance.pfloInput[pflo.bpmnPfloTaskId].args; let i = index")

+                    mat-expansion-panel-header(style="align-text:center") {{tiNameLookup[workReq.testInstanceId]}}

+                      //button.primary.mr-1.ml-4(mat-mini-fab, aria-label='Remove', color="warn", (click)="deleteWorkReq(pflo.bpmnPfloTaskId, i)")

+                      i.fa.fa-remove.ml-2((click)="deleteWorkReq(pflo.bpmnPfloTaskId, i)")

+                    app-workflow-request([formData]='testInstance.pfloInput[pflo.bpmnPfloTaskId].args[i]', [taskId]="pflo.bpmnPfloTaskId", [index]="i",  (childEvent)="saveWorkReqForm($event)")

+.row(style="height:40px")

+.row.form-buttons(*ngIf = "selectedDefinition.testName")

+  .col-12.mt-3

+    .pull-right

+      h5.mr-2(style="color: Red", *ngIf='executionFailed') Tests failed to execute! 

+      button.mr-2(mat-raised-button, *ngIf='!editMode', color="primary", (click)="saveAll()") Save

+      button(mat-raised-button, *ngIf='!editMode', color="warn", (click)="saveAndExecute()") Save and Execute

+      button.mr-2(mat-raised-button, *ngIf='editMode', color="primary", (click)="updateInstance()") Update

+      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
index 0000000..d93cec9
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.scss
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.dropdown-toggle::after {

+    display:none;

+}
\ 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
index 0000000..16c79dd
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { CreateTestInstanceFormComponent } from './create-test-instance-form.component';

+

+describe('CreateTestInstanceFormComponent', () => {

+  let component: CreateTestInstanceFormComponent;

+  let fixture: ComponentFixture<CreateTestInstanceFormComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ CreateTestInstanceFormComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(CreateTestInstanceFormComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..df703b4
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.component.ts
@@ -0,0 +1,788 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

+import 'codemirror/mode/yaml/yaml.js';

+import { TestInstanceService } from '../../services/test-instance.service';

+import { TestDefinitionService } from '../../services/test-definition.service';

+import { SchedulingService } from '../../services/scheduling.service';

+import { SelectStrategyModalComponent } from '../select-strategy-modal/select-strategy-modal.component';

+import { MatDialog, MatSnackBar } from '@angular/material';

+import { AlertModalComponent } from '../alert-modal/alert-modal.component';

+import { Router } from '@angular/router';

+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';

+import { ListService } from 'app/shared/services/list.service';

+import { FileUploader, FileItem, ParsedResponseHeaders } from 'ng2-file-upload';

+import { HttpClient, HttpHeaders } from "@angular/common/http";

+import { AppGlobals } from "../../../app.global";

+import { CookieService } from "ngx-cookie-service";

+import * as  YAML from '../../../../../../node_modules/yamljs/lib/Yaml';

+import 'codemirror/mode/javascript/javascript.js';

+import beautify from 'json-beautify';

+import { WorkflowRequest } from './instance.class';

+import { PfloInputClass } from './instance.class';

+import { GroupService } from 'app/shared/services/group.service';

+import { ExecuteService } from 'app/shared/services/execute.service';

+

+const URL = AppGlobals.baseAPIUrl + 'files';

+

+

+@Component({

+  selector: 'app-create-test-instance-form',

+  templateUrl: './create-test-instance-form.component.pug',

+  styleUrls: ['./create-test-instance-form.component.scss']

+})

+export class CreateTestInstanceFormComponent implements OnInit {

+  yaml;

+  //Variable sent between modules

+  @Input() public existingInstance: any;

+

+  @Output() public childEvent = new EventEmitter();

+  public dataTemplate: any;

+  public configTemplate: any;

+

+  public codeConfig = {

+    mode: "yaml",

+    theme: "eclipse",

+    lineNumbers: true

+  };

+

+  public codeJsonConfig = {

+    mode: "application/json",

+    theme: "eclipse",

+    lineNumbers: true

+  }

+

+  public testDefinition;

+  public testInstance;

+  public createResult;

+  public selectedDefinition;

+  public errorCount = 0;

+  public executionFailed = false;

+  public editMode = false;

+  public httpOptions;

+  public selectedBpmn;

+  public uploader: FileUploader;

+  public isZip = true;

+  public scriptFiles = [];

+  public uploaders = {};

+  public vthInput = {};

+  public pfloInput = {};

+  public argsToAdd = {};

+  public vthInputYaml = {};

+  public displayYAML = false;

+  public testHeadYAML = false;

+  public testHeadNames = {};

+  public tiNameLookup = {};

+  public instances;

+  public search;

+  public instanceAdded;

+  

+

+  public uploadOptions = {

+    url: AppGlobals.baseAPIUrl + 'file-transfer',

+    authTokenHeader: 'Authorization',

+    authToken: 'Bearer ' + JSON.parse(this.cookie.get('access_token'))

+  };

+

+  // , private http: HttpClient, private Params: ParamsService, private cookie: CookieService

+  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) {

+    this.http = http;

+    this.cookie = cookie;

+    // this.httpOptions = {

+    //     headers: new HttpHeaders({ 

+    //       'Content-Type': 'application/json',

+    //       'Authorization': 'Bearer ' + JSON.parse(this.cookie.get('access_token'))

+    //     })

+    //   };

+  }

+  // testingSelect(){

+  //   console.log(this.selectedBpmn);

+  // }

+  myFilter(bpmn) {

+    return bpmn.isDeployed;

+  }

+  ngOnInit() {

+    this.search = {};

+    this.search.testInstanceName = '';

+    this.testInstance = {};

+    this.selectedDefinition = {};

+    this.selectedBpmn = {};

+    this.testInstance.useLatestTestDefinition = true;

+    this.testInstance.simulationVthInput = {};

+    let currentGroup;

+    //options required for the file uploader

+    currentGroup = this.groupService.getGroup();

+    this.groupService.groupChange().subscribe(group => {

+      currentGroup = group;

+    });

+    

+    this.testInstanceService.find({

+      groupId: currentGroup['_id'],

+      $limit: -1,

+      $sort: {

+        createdAt: -1,

+      },

+      $select: ['testInstanceName']

+    }).subscribe((result) => {

+      this.instances = result;

+      for(let i = 0; i < this.instances.length; i++){

+        this.instances[i].isSelected = false;

+      }

+    })

+

+    //File Uploaders

+    //this.uploader = new FileUploader(uploadOptions);

+    //if the user is using this page for editing an existing instance

+    if (this.existingInstance) {

+      //console.log(this.existingInstance)

+      if (this.existingInstance.testInstance) {

+        this.testInstance = this.existingInstance.testInstance;

+        this.selectedDefinition = this.existingInstance.testInstance['testDefinitionId'];

+        

+        this.convertSimulationVth('string');

+        console.log(this.testInstance);

+

+        //set the bpmn to the selected bpmn. Alert User if no bpmn versions are deployed

+        if (this.testInstance.useLatestTestDefinition) {

+          this.useLatest();

+        } else {

+          for (let i = 0; i < this.selectedDefinition.bpmnInstances.length; i++) {

+            if (this.selectedDefinition.bpmnInstances[i].processDefintionId === this.testInstance.processDefintionId) {

+              this.selectedBpmn = this.selectedDefinition.bpmnInstances[i];

+              break;

+            }

+          }

+        }

+

+        if (this.testInstance.testData === '') {

+          this.displayYAML = true;

+        }

+

+        if (!this.testInstance.simulationVthInput) {

+          this.testInstance.simulationVthInput = {};

+        }

+

+

+        //grab all robot test heads to assign uploaders to each and create the vthInput object

+        //for(let j = 0; j < this.selectedBpmn.testHeads.length; j++){

+

+

+        //}

+        //console.log(this.uploaders);

+        if (this.existingInstance.isEdit == true)

+          this.editMode = true;

+      }//if the user is creating a new instance from the test definition page

+      else if (this.existingInstance.testDefinition) {

+        this.selectedDefinition = this.existingInstance.testDefinition;

+        this.populateTIName();

+        //set the bpmn as the latest deployed version. Alert User if no bpmn versions are deployed

+        this.useLatest();

+        this.populateVthInput();

+        this.populatePfloInput();

+        //grab all robot test heads to assign uploaders to each and set the vthInput object

+        for (let j = 0; j < this.selectedBpmn.testHeads.length; j++) {

+

+          if (this.selectedBpmn.testHeads[j].testHeadId['testHeadName'].toLowerCase() === 'robot') {

+            this.uploaders[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = new FileUploader(this.uploadOptions);

+          }

+        }

+

+        this.testInstance = {

+          "testInstanceDescription": "",

+          "testDefinitionId" : this.selectedDefinition["_id"],

+          "vthInput" : this.vthInput,

+          "pfloInput": this.pfloInput,

+          "vthInputYaml": this.vthInputYaml,

+          "testData": this.selectedBpmn.testDataTemplate,

+          "testDataJSON": this.selectedBpmn.testDataTemplateJSON,

+          "useLatestTestDefinition": true,

+          "internalTestData": {},

+          "simulationVthInput": {}

+        };

+

+      }

+    }

+    

+  }

+

+  convertSimulationVth(convertTo) {

+    for (let key in this.testInstance.simulationVthInput) {

+      if (this.testInstance.simulationVthInput.hasOwnProperty(key)) {

+        if(convertTo == 'json')

+          this.testInstance.simulationVthInput[key] = JSON.parse(this.testInstance.simulationVthInput[key]);

+        else if (convertTo == 'string')

+          this.testInstance.simulationVthInput[key] = beautify(this.testInstance.simulationVthInput[key], null, 2, 10);

+      }

+    }

+

+  }

+ 

+

+

+  simulationMode() {

+    let def = {

+      delay: 0, response: {}

+    };

+    //console.log(this.selectedBpmn);

+    if (this.testInstance.simulationMode) {

+      this.selectedBpmn.testHeads.forEach(e => {

+        if(!this.testInstance.simulationVthInput){

+          this.testInstance.simulationVthInput = {}

+        }

+        if (!this.testInstance.simulationVthInput[e.bpmnVthTaskId]) {

+          this.testInstance.simulationVthInput[e.bpmnVthTaskId] = beautify(def, null, 2, 10);

+        }

+      })

+    }

+  }

+

+  populateTIName() {

+    let list;

+    this.testInstanceService.find({ $limit: -1, $select: ['testInstanceName'], testDefinitionId: this.selectedDefinition._id }).subscribe((res) => {

+      list = res;

+      //console.log(list);

+      let num = list.length;

+      if (num === 0) {

+        this.testInstance.testInstanceName = this.selectedDefinition.testName;

+      } else {

+        this.testInstance.testInstanceName = this.selectedDefinition.testName + num;

+      }

+      let isTaken = true;

+      let count = 0;

+      let alreadyExisted = false;

+      while (isTaken === true && count < 10000) {

+        for (let i = 0; i < list.length; i++) {

+          if (list[i]["testInstanceName"] === this.testInstance.testInstanceName) {

+            num++;

+            this.testInstance.testInstanceName = this.selectedDefinition.testName + num;

+            alreadyExisted = true;

+            break;

+          }

+        }

+        if (alreadyExisted) {

+          alreadyExisted = false;

+        } else {

+          isTaken = false;

+        }

+        count++;

+      }

+    });

+  }

+  //Section for implementing Paralell workflow data entry --------------------------------------------------------------------------------------

+  populatePfloInput(){

+    // this.pfloInput = {

+    //   "task123": new PfloInputClass

+    // }

+    //this.selectedBpmn.pflos = [{"bpmnPfloTaskId" : "task123", "label": "TestPFLO"}]

+    

+    if(this.testInstance.pfloInput){  

+      return;

+    }

+

+    this.pfloInput = {};

+   

+    if(this.selectedBpmn == {} || !this.selectedBpmn.pflos){

+      

+      this.testInstance.pfloInput = this.pfloInput;

+      return;

+    }

+

+    for(let i = 0; i < this.selectedBpmn.pflos.length; i++){

+      if(this.selectedBpmn.pflos[i]['bpmnPfloTaskId'] != null){

+        this.pfloInput[this.selectedBpmn.pflos[i]['bpmnPfloTaskId']] = new PfloInputClass;

+       

+        //this.pfloInput[this.selectedBpmn.pflos[i]['bpmnPfloTaskId'] + "pfloName"] = this.selectedBpmn.pflos[i]['label'];

+      }

+    }

+    this.testInstance.pfloInput = this.pfloInput;

+    

+  }

+

+  

+  addInstancesToPflo(taskId){

+    for(let i = 0; i < this.instances.length; i++){

+      if(this.instances[i].isSelected){

+        this.tiNameLookup[this.instances[i]._id] = this.instances[i].testInstanceName;

+        this.addPfloInput(taskId, this.instances[i]._id);

+      }

+

+    }

+  }

+

+  addPfloInput(taskId, instanceId){

+     

+    this.testInstance.pfloInput[taskId].args.push(new WorkflowRequest(instanceId));

+   

+  }

+

+  clearSelectedValues(){

+    this.search.testInstanceName = '';

+    for(let i = 0; i < this.instances.length; i++){

+      this.instances[i].isSelected = false;

+    }

+  }

+

+  saveTestDataOptions(event) {

+    this.testInstance.testData = event.object;

+  }

+

+  saveFormOptions(event) {

+    this.testInstance.vthInput[event.taskId] = event.object;

+    //console.log(this.testInstance);

+  }

+

+  

+  checkPfloInputLength(){

+  

+    if(this.testInstance.pfloInput != null){

+      let temp =  Object.keys(this.testInstance.pfloInput);

+      if(temp.length)

+        return temp.length > 0;

+      else

+        return false;

+    }else{

+      return false;

+    }

+  }

+

+  deleteWorkReq(pfloId, index){

+    this.testInstance.pfloInput[pfloId].args.splice(index, 1);

+    //FORCE REFRESH all connected forms to update their index

+  }

+

+  saveWorkReqForm(event) {

+    this.testInstance.pfloInput[event.taskId].args[event.index] = event.object;

+    //console.log(this.testInstance);

+  }

+

+  convertTestLevelYaml() {

+    if (this.displayYAML) {

+      this.testInstance.testDataJSON = YAML.parse(this.testInstance.testData);

+    } else {

+      this.testInstance.testData = YAML.stringify(this.testInstance.testDataJSON);

+    }

+  }

+

+  convertVTHYaml() {

+    if (this.testHeadYAML) {

+      for (let key in this.testInstance.vthInputYaml) {

+        this.testInstance.vthInput[key] = YAML.parse(this.testInstance.vthInputYaml[key]);

+      }

+    } else {

+

+      for (let key in this.testInstance.vthInput) {

+        this.testInstance.vthInputYaml[key] = YAML.stringify(this.testInstance.vthInput[key]);

+      }

+    }

+  }

+  //End of Paralell workflow data entry section --------------------------------------------------------------------------------------

+

+  changeBpmn() {

+    //populate the vth inputs when bpmn changes

+    this.populateVthInput();

+    this.displayYAML = !this.displayYAML;

+    this.testInstance.testDataJSON = this.selectedBpmn.testDataTemplateJSON;

+    this.testInstance.testData = this.selectedBpmn.testDataTemplate;

+    this.convertTestLevelYaml();

+    setTimeout(() => {

+      this.displayYAML = !this.displayYAML;

+    }, 200);

+

+  }

+  //toggle Yaml for test level data

+  toggleYaml() {

+    this.convertTestLevelYaml();

+    this.displayYAML = !this.displayYAML;

+  }

+  //toggles Yaml for testHeads

+  testHeadYaml() {

+    this.convertVTHYaml();

+    this.testHeadYAML = !this.testHeadYAML;

+  }

+  //onChange method for the use latest TD toggle

+  useLatest() {

+    if (this.testInstance.useLatestTestDefinition) {

+      let temp;

+      let orderNum;

+      let processKey;

+      for (let i = 0; i < this.selectedDefinition.bpmnInstances.length; i++) {

+        if (temp) {

+          processKey = this.selectedDefinition.bpmnInstances[i].processDefinitionId

+          if(processKey){

+            orderNum = processKey.split(":");

+            orderNum = orderNum[1];

+            //console.log("bpmn check : " + orderNum + " bpmn current: " + temp.processDefinitionId.split(':')[1]);

+            if (this.selectedDefinition.bpmnInstances[i].isDeployed && parseInt(orderNum) > parseInt(temp.processDefinitionId.split(':')[1])) {

+              temp = this.selectedDefinition.bpmnInstances[i];

+            }

+          }

+        } else {

+          if (this.selectedDefinition.bpmnInstances[i].isDeployed) {

+            temp = this.selectedDefinition.bpmnInstances[i];

+          }

+        }

+

+      }

+      if (temp.isDeployed) {

+        this.selectedBpmn = temp;

+      } else {

+        this.dialog.open(AlertModalComponent, {

+          width: '450px',

+          data: {

+            type: 'alert',

+            message: 'Test Definition does not contain a deployed bpmn instance. Please return to the Test Definition page and deploy.'

+          }

+        });

+        this.testInstance.useLatestTestDefinition = false;

+      }

+      this.populateVthInput();

+    }

+    this.populatePfloInput();

+  }

+

+ 

+  //checks if the test instance has a required Name

+  allNamed() {

+    if (!this.testInstance.testInstanceName) {

+      return false;

+    }

+

+    return true;

+  }

+  

+  //populate the vthInputYaml for newly created testInstances

+  populateVthInput() {

+    this.vthInputYaml = {};

+    this.vthInput = {};

+    for (let i = 0; i < this.selectedBpmn.testHeads.length; i++) {

+      this.vthInputYaml[this.selectedBpmn.testHeads[i].bpmnVthTaskId] = this.selectedBpmn.testHeads[i].testHeadId.vthInputTemplate;

+      this.vthInputYaml[this.selectedBpmn.testHeads[i].bpmnVthTaskId + "testHeadName"] = this.selectedBpmn.testHeads[i].testHeadId.testHeadName;

+      if (this.selectedBpmn.testHeads[i].testHeadId.vthInputTemplateJSON) {

+        this.vthInput[this.selectedBpmn.testHeads[i].bpmnVthTaskId] = this.selectedBpmn.testHeads[i].testHeadId.vthInputTemplateJSON;

+        this.vthInput[this.selectedBpmn.testHeads[i].bpmnVthTaskId + "testHeadName"] = this.selectedBpmn.testHeads[i].testHeadId.testHeadName;

+      }

+

+    }

+

+  }

+  //Used to grab all test definitions for the user to select.

+  getDefinition() {

+    const dialogRef = this.dialog.open(SelectStrategyModalComponent, {

+      width: '450px',

+      data: {}

+    });

+

+    dialogRef.afterClosed().subscribe(result => {

+      //If the user already had a selected definition and selected a new one, prompt the user to be sure of change

+      if (result != '' && this.selectedDefinition.testName) {

+        this.dialog.open(AlertModalComponent, {

+          width: '450px',

+          data: {

+            type: 'confirmation',

+            message: 'Changing the Test Definition will erase the Instance you are currently writing.'

+          }

+        }).afterClosed().subscribe(response => {

+          if (response) {

+            this.selectedDefinition = result;            

+            this.populateTIName();

+            //set the bpmn as the latest deployed version. Alert User if no bpmn versions are deployed

+            this.useLatest();

+            this.populateVthInput();

+            this.populatePfloInput();

+            //grab all robot test heads to assign uploaders to each and initialize vthInput

+            for (let j = 0; j < this.selectedBpmn.testHeads.length; j++) {

+              if (this.selectedBpmn.testHeads[j].testHeadId['vthInputTemplateJSON']) {

+                this.vthInput[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = this.selectedBpmn.testHeads[j].testHeadId['vthInputTemplateJSON'];

+              }

+

+              if (this.selectedBpmn.testHeads[j].testHeadId['testHeadName'].toLowerCase() === 'robot') {

+                this.uploaders[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = new FileUploader(this.uploadOptions);

+              }

+            }

+

+            this.testInstance = {

+              "testInstanceDescription": "",

+              "groupId": this.selectedDefinition["groupId"],

+              "testDefinitionId": this.selectedDefinition["_id"],

+              "vthInput": this.vthInput,

+              "pfloInput": this.pfloInput,

+              "vthInputYaml": this.vthInputYaml,

+              "testData": this.selectedBpmn.testDataTemplate,

+              "testDataJSON": this.selectedBpmn.testDataTemplateJSON,

+              "useLatestTestDefinition": true,

+              "internalTestData": {},

+              "simulationVthInput": {}

+

+            };

+          }

+        });

+

+        //else they did not have a test definition currently selected

+      } else {

+        this.selectedDefinition = result;

+        this.populateTIName();

+        //set the bpmn as the latest deployed version. Alert User if no bpmn versions are deployed

+        this.useLatest();

+        this.populateVthInput();

+        this.populatePfloInput();

+        //grab all robot test heads to assign uploaders to each

+        for (let j = 0; j < this.selectedBpmn.testHeads.length; j++) {

+          if (this.selectedBpmn.testHeads[j].testHeadId['vthInputTemplateJSON']) {

+            this.vthInput[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = this.selectedBpmn.testHeads[j].testHeadId['vthInputTemplateJSON'];

+          }

+          if (this.selectedBpmn.testHeads[j].testHeadId['testHeadName'].toLowerCase() === 'robot') {

+            this.uploaders[this.selectedBpmn.testHeads[j].bpmnVthTaskId] = new FileUploader(this.uploadOptions);

+          }

+        }

+             

+          

+         

+          this.testInstance = {

+            "testInstanceDescription": "",

+              "groupId": this.selectedDefinition["groupId"],

+              "testDefinitionId": this.selectedDefinition["_id"],

+              "vthInput": this.vthInput,

+              "pfloInput": this.pfloInput,

+              "vthInputYaml": this.vthInputYaml,

+              "testData": this.selectedBpmn.testDataTemplate,

+              "testDataJSON": this.selectedBpmn.testDataTemplateJSON,

+              "useLatestTestDefinition": true,

+              "internalTestData": {},

+              "simulationVthInput": {}

+          };

+          

+        }

+    });

+  }

+

+  //Saves the Test Instance Object to the database 

+  saveAll() {

+    if (!this.allNamed()) {

+      this.dialog.open(AlertModalComponent, {

+        width: '450px',

+        data: {

+          type: 'alert',

+          message: 'The Instance is not named! Please ensure the Instance are named.'

+        }

+      }).afterClosed().subscribe((result) => {

+        return;

+      });

+    } else {

+

+      if (!this.testInstance.processDefinitionId) {

+        this.testInstance.processDefinitionId = this.selectedBpmn.processDefinitionId;

+      }

+      this.errorCount = 0;

+      if (!this.displayYAML) {

+        this.testInstance.testData = this.testInstance.testDataJSON;

+      }

+      if (this.testHeadYAML) {

+        this.testInstance.vthInput = this.testInstance.vthInputYaml;

+      }

+

+      this.convertSimulationVth('json');

+

+      this.testInstanceService.create(this.testInstance)

+        .subscribe(

+          (result) => {

+            if (Object.keys(this.uploaders).length > 0)

+              this.uploadFiles(result);

+

+            this.snack.openFromComponent(AlertSnackbarComponent, {

+              duration: 1500,

+              data: {

+                message: 'Test Instance Saved'

+              }

+            });

+            this.dialog.closeAll();

+          },

+          (error) => {

+            this.dialog.open(AlertModalComponent, {

+              width: '450px',

+              data: {

+                type: 'Alert',

+                message: error

+              }

+            });

+

+          });

+    }

+  }

+

+  updateInstance() {

+

+

+    if (!this.testInstance.processDefinitionId) {

+      this.testInstance.processDefinitionId = this.selectedBpmn.processDefinitionId;

+    }

+    this.errorCount = 0;

+    if (!this.displayYAML) {

+      this.testInstance.testData = this.testInstance.testDataJSON;

+    }

+    if (this.testHeadYAML) {

+      this.testInstance.vthInput = this.testInstance.vthInputYaml;

+    }

+

+    this.convertSimulationVth('json');

+

+    this.testInstanceService.update(this.testInstance)

+      .subscribe((result) => {

+        this.snack.openFromComponent(AlertSnackbarComponent, {

+          duration: 1500,

+          data: {

+            message: 'Test Instance Updated'

+          }

+        });

+        this.childEvent.emit();

+      });

+  }

+

+  cancel() {

+    this.childEvent.emit();

+  }

+  uploadFiles(result) {

+    for (let i = 0; i < this.selectedBpmn.testHeads.length; i++) {

+      if (!this.uploaders[this.selectedBpmn.testHeads[i].bpmnVthTaskId]) {

+        continue;

+      }

+      let key = this.selectedBpmn.testHeads[i].bpmnVthTaskId;

+      let uploader = this.uploaders[key];

+      if (uploader.queue.length > 0) {

+        uploader.uploadAll();

+        uploader.onCompleteItem = (item: FileItem, response: string, status: Number, headers: ParsedResponseHeaders) => {

+          this.scriptFiles.push(JSON.parse(response)[0]);

+        }

+      }

+      uploader.onCompleteAll = () => {

+

+        let scriptFilesId = [];

+        for (let i = 0; i < this.scriptFiles.length; i++) {

+          scriptFilesId.push(this.scriptFiles[i]['_id']);

+        }

+       

+        for (let i = 0; i < this.selectedBpmn.testHeads.length; i++) {

+          if (this.selectedBpmn.testHeads[i].testHeadId['testHeadName'].toLowerCase() === 'robot') {

+            this.testInstance.internalTestData[this.selectedBpmn.testHeads[i].bpmnVthTaskId] =

+              {

+                "robotFileId": scriptFilesId[0]

+              };

+          }

+        }

+        let ti = {

+          '_id': result._id,

+          'internalTestData': this.testInstance.internalTestData

+        }

+

+        this.testInstanceService.patch(ti).subscribe(

+          res => {

+            //console.log(res);

+            // resolve(res);

+          },

+          err => {

+            // console.log(err);

+            // reject(err);

+          }

+        );

+      }

+    }

+  }

+  //saves instance to the database and executes the test using the agenda scheduler

+  saveAndExecute() {

+    if (!this.allNamed()) {

+      this.dialog.open(AlertModalComponent, {

+        width: '450px',

+        data: {

+          type: 'alert',

+          message: 'One or more Instance is not named! Please ensure all Instances are named.'

+        }

+      }).afterClosed().subscribe((result) => {

+        return;

+      });

+    } else {

+

+      if (!this.testInstance.processDefinitionId) {

+        this.testInstance.processDefinitionId = this.selectedBpmn.processDefinitionId;

+      }

+      this.errorCount = 0;

+      if (!this.displayYAML) {

+        this.testInstance.testData = this.testInstance.testDataJSON;

+      }

+      if (this.testHeadYAML) {

+        this.testInstance.vthInput = this.testInstance.vthInputYaml;

+      }

+

+      this.convertSimulationVth('json')

+

+      this.testInstanceService.create(this.testInstance)

+        .subscribe(

+          (result) => {

+            this.executionFailed = false;

+            this.createResult = result;

+            if (Object.keys(this.uploaders).length > 0)

+              this.uploadFiles(result);

+

+

+            this.execute.create({

+              _id: this.createResult._id,

+              async: true

+            })

+              .subscribe(

+                (response) => {

+

+                  this.childEvent.emit();

+                  this.snack.openFromComponent(AlertSnackbarComponent, {

+                    duration: 1500,

+                    data: {

+                      message: 'Test Instance Saved and Executed'

+                    }

+                  });

+                  this.router.navigateByUrl('/dashboard');

+                },

+                (error) => {

+                  this.executionFailed = true;

+                  this.dialog.open(AlertModalComponent, {

+                    width: '450px',

+                    data: {

+                      type: 'Alert',

+                      message: "Execution error: " + error

+                    }

+                  });

+                });

+          },

+          (error) => {

+            this.dialog.open(AlertModalComponent, {

+              width: '450px',

+              data: {

+                type: 'Alert',

+                message: "Save Error: " + error

+              }

+            });

+          });

+    }

+  }

+

+  createNewInstance() {

+    this.testInstance = {

+      'testInstanceName': '',

+      'testInstanceDescription': '',

+      'testDefinitionId': this.selectedDefinition._id,

+      'testData': '',

+      'simulationVthInput': {}

+

+    }

+  }

+}

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
index 0000000..875e328
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { CreateTestInstanceFormModule } from './create-test-instance-form.module';

+

+describe('CreateTestInstanceFormModule', () => {

+  let createTestInstanceFormModule: CreateTestInstanceFormModule;

+

+  beforeEach(() => {

+    createTestInstanceFormModule = new CreateTestInstanceFormModule();

+  });

+

+  it('should create an instance', () => {

+    expect(createTestInstanceFormModule).toBeTruthy();

+  });

+});

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
index 0000000..3daf9ad
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/create-test-instance-form.module.ts
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { CreateTestInstanceFormComponent } from './create-test-instance-form.component';

+import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { FormsModule } from '@angular/forms';

+import { MatButtonModule, MatDialogModule, MatCheckboxModule, MatRadioModule, MatInputModule, MatIconModule, MatExpansionModule, MatCardModule, MatOptionModule, MatSnackBarModule, MatProgressBar, MatSlideToggleModule, MatSelectModule } from '@angular/material';

+import { CodemirrorModule } from 'ng2-codemirror';

+import { SelectStrategyModalModule } from '../select-strategy-modal/select-strategy-modal.module';

+import { AlertModalModule } from '../alert-modal/alert-modal.module';

+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';

+import { FormGeneratorModule } from '../form-generator/form-generator.module';

+import { FileUploadModule } from 'ng2-file-upload';

+import { FilterNonDeployedPipe } from './filterNonDeployed.pipe';

+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';

+import { WorkflowRequestModule } from '../workflow-request/workflow-request.module';

+

+

+

+@NgModule({

+  imports: [

+    CommonModule,

+    AlertModalModule,

+    SelectStrategyModalModule,

+    PerfectScrollbarModule,

+    FilterPipeModule,

+    FormsModule,

+    MatButtonModule,

+    MatDialogModule,

+    MatCheckboxModule,

+    MatRadioModule,

+    MatInputModule,

+    MatSlideToggleModule,

+    MatSelectModule,

+    MatOptionModule,

+    MatIconModule,

+    MatExpansionModule,

+    MatCardModule,

+    MatSnackBarModule,

+    AlertSnackbarModule,

+    CodemirrorModule,

+    FormGeneratorModule,

+    FileUploadModule,

+    NgbModule,

+    WorkflowRequestModule

+

+

+  ],

+  declarations: [CreateTestInstanceFormComponent, FilterNonDeployedPipe],

+  exports: [CreateTestInstanceFormComponent]

+})

+export class CreateTestInstanceFormModule {

+

+ }

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
index 0000000..87a33c3
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/filterNonDeployed.pipe.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Pipe, PipeTransform } from '@angular/core';

+

+@Pipe({

+    name: 'filterNonDeployed',

+    pure: false

+})

+export class FilterNonDeployedPipe implements PipeTransform {

+    transform(items: any[], callback: (item: any) => boolean): any {

+        if (!items || !callback) {

+            return items;

+        }

+        // filter items array, items which match and return true will be

+        // kept, false will be filtered out

+        return items.filter(item => callback(item) );

+    }

+}
\ 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
index 0000000..13af570
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/create-test-instance-form/instance.class.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+export class TestInstance {

+

+}

+

+export class WorkflowRequest {

+    public async = false;

+    public asyncTopic = "";

+    public executorId = "";

+    public testInstanceId = "";

+    public pfloInput : null;

+    public testData : null;

+    public vthInput : null;

+    public maxExecutionTimeInMillis = 0;

+

+    constructor(instanceId){

+        this.testInstanceId = instanceId;

+    }

+}

+

+export class PfloInputClass {

+    public args = [];

+    public interruptOnFailure = false;

+    public maxFailures = 0;

+    public threadPoolSize = 2;

+}

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
index 0000000..c8a4884
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.html
@@ -0,0 +1,23 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<form [formGroup]="form" style="text-align:center" #test >

+

+

+</form>

+<div class="col-md-12" >

+    <h5 [hidden]="!noData()">No Input Template provided.</h5>

+</div>

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..0ba7d15
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { FormGeneratorComponent } from './form-generator.component';

+

+describe('FormGeneratorComponent', () => {

+  let component: FormGeneratorComponent;

+  let fixture: ComponentFixture<FormGeneratorComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ FormGeneratorComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(FormGeneratorComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..6f205db
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.component.ts
@@ -0,0 +1,381 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, Output, EventEmitter, ViewChild, NgModuleRef, Injector, Compiler, NgModule, ViewContainerRef } from '@angular/core';

+import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';

+import { MatSnackBar } from '@angular/material';

+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';

+

+

+@Component({

+  selector: 'app-form-generator',

+  templateUrl: './form-generator.component.html',

+  styleUrls: ['./form-generator.component.scss']

+})

+export class FormGeneratorComponent implements OnInit {

+

+  public test = {

+    text1: "Hello please enter text1",

+    text2: "Hello please enter text2",

+    one: {

+      text1: "lol"

+    },

+    siteSpecific: {

+      port: "1234",

+      host: "google.com"

+    },

+    list: ["Enter", "some", "values"],

+    list2: [{"test": "hello"}, {"test2": "hello2"}]

+  }

+  public isSaved = false;

+  public arrayCheck;

+  @Input() public JSONData : any;

+  @Input() public taskId: any;

+  @ViewChild('test', { read: ViewContainerRef }) public containerDiv;

+  @Output() public childEvent = new EventEmitter();

+

+  form = new FormGroup({

+  });

+

+  //public textAreaTemplate = "<textarea [(value)]='";

+  /*

+  constructor(private _compiler: Compiler,

+            private _injector: Injector,

+            private _m: NgModuleRef<any>) {

+}

+

+ngAfterViewInit() {

+  const template = '<span>generated on the fly: {{name}}</span>';

+

+  const tmpCmp = Component({template: template})(class {

+  });

+  const tmpModule = NgModule({declarations: [tmpCmp]})(class {

+  });

+

+  this._compiler.compileModuleAndAllComponentsAsync(tmpModule)

+    .then((factories) => {

+      const f = factories.componentFactories[0];

+      const cmpRef = this.vc.createComponent(f);

+      cmpRef.instance.name = 'dynamic';

+    })

+}

+  */

+ 

+  //public containerDiv;// = document.getElementById(tas);

+  constructor(private compiler: Compiler,

+    private injector: Injector,

+    private snack: MatSnackBar,

+    private m: NgModuleRef<any>) { }

+    public testing = "hello";

+    //public textAreaTemplate = '';

+  ngOnInit() {

+   

+   //this.JSONData = this.test;

+    if(this.JSONData){

+      this.arrayCheck = JSON.parse(JSON.stringify(this.JSONData));

+      let arr = [];

+      this.populatePage(arr, 1);

+      this.onFormChange();

+    }

+  }

+

+  onFormChange(){

+    

+    this.form.valueChanges.subscribe(val => {

+      this.copyValues([]);

+      

+      let event = {

+        object: this.JSONData,

+        taskId: this.taskId

+      };

+      this.childEvent.emit(event);

+    });

+  }

+  //checks if data was supplied to form

+  noData(){

+   

+    if(Object.keys(this.form.controls).length == 0){

+      return true;

+    }else{

+      return false;

+    }

+  }

+

+  copyValues(keyArr){

+    // console.log("Fixed");

+    let data = this.JSONData;

+    let tempArrCheck = this.arrayCheck;

+    let keyPath = "";

+    for(let k in keyArr){

+      tempArrCheck = tempArrCheck[keyArr[k]];

+      data = data[keyArr[k]];

+      keyPath += keyArr[k];

+    }

+    

+    for(let key in data){

+      if(this.form.get(keyPath + key)){

+        if(tempArrCheck[key] === "_ConvertThisArray_"){

+          let temp = this.form.get(keyPath + key).value;

+          data[key] = temp.split(',');

+        }else{

+          data[key] = this.form.get(keyPath + key).value;

+        }

+      }else{

+        keyArr.push(key);

+        this.copyValues(keyArr);

+        keyArr.splice(keyArr.length - 1);

+      }

+    }

+    // Object.keys(this.form.controls).forEach(key => {

+    //   data[key] = this.form.get(key).value;

+    // });

+    

+  }

+

+  populatePage(keyArr, level){//vthinput and testInput

+    let data = this.JSONData;

+    //used to detect and convert arrays after input is entered

+    let tempArrCheck = this.arrayCheck;

+    let keyPath = "";

+    for(let k in keyArr){

+      tempArrCheck = tempArrCheck[keyArr[k]];

+      data = data[keyArr[k]];

+      keyPath += keyArr[k];

+    }

+    //console.log(data);

+    

+    for( let key in data){

+      let indent = 'ml-' + level;

+      

+      if((typeof data[key] === "object" && !data[key].length) || (typeof data[key] === "object" && data[key].length && 

+        typeof data[key][0] === "object")){

+        

+        let str = '';

+        if(level >= 4){

+          str = 'h5';

+          //indent = 'ml-5';

+        }else if(level === 3){

+          str = 'h4';

+          //indent = 'ml-4';

+        }else if (level === 2){

+          str = 'h3';

+          //indent = 'ml-3';

+        }else{

+          str = 'h2'

+          //indent = 'ml-2';

+        }

+        if(data.constructor === Array){

+          

+          keyArr.push(key);

+          this.populatePage(keyArr, level);

+          keyArr.splice(keyArr.length - 1);

+          continue;

+        }

+        const textHeaderTemplate = '<' + str + ' class="'+ indent +'" style="font-weight:bold">' + key.trim() + '</'+ str + '>';

+        const tmpCmp = Component({template: textHeaderTemplate})(class {

+        });

+        const tmpModule = NgModule({imports:[ReactiveFormsModule] ,declarations: [tmpCmp]})(class {

+        });

+             

+        this.compiler.compileModuleAndAllComponentsAsync(tmpModule)

+          .then((factories) => {

+            const f = factories.componentFactories[0];

+            const cmpRef = this.containerDiv.createComponent(f);

+          })

+        

+        keyArr.push(key);

+        level ++;

+        this.populatePage(keyArr, level);

+        level = level -1;

+        keyArr.splice(keyArr.length - 1);

+      }

+      else if(typeof data[key] === "string"){

+       // this.containerDiv.

+     

+       this.form.addControl(keyPath + key.trim(), new FormControl(data[key]));

+      

+        if(level > 1){

+          

+          const textInputTemplate = '<div class="  mb-1 '+ indent + '" [formGroup]="form"> <label class="mr-2">' + key.trim() + '</label><input formControlName="' + keyPath + key.trim() + '"> </div>';

+          const tmpCmp = Component({template: textInputTemplate})(class {

+          });

+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {

+          });

+          

+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();

+          

+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)

+            .then((factories) => {

+              const f = factories.componentFactories[0];

+              const cmpRef = this.containerDiv.createComponent(f);

+              cmpRef.instance.form = this.form;

+            })

+        }

+        else{

+          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>';

+          const tmpCmp = Component({template: textInputTemplate})(class {

+          });

+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {

+          });

+          

+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();

+          

+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)

+            .then((factories) => {

+              const f = factories.componentFactories[0];

+              const cmpRef = this.containerDiv.createComponent(f);

+              cmpRef.instance.form = this.form;

+            })

+        }

+       

+        

+      }

+      else if(typeof data[key] === "object" && data[key].length){

+        

+        //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();

+        let temp = "";

+        for(let i = 0; i < data[key].length; i++){

+          if(i != data[key].length - 1)

+            temp += data[key][i] + ",";

+          else

+            temp += data[key][i] + "";

+        }

+        //this.containerDiv.element.nativeElement.appendChild(document.createElement("textarea")).innerHTML = temp.trim();

+        this.form.addControl(keyPath + key.trim(), new FormControl(temp));

+        

+        tempArrCheck[key] = "_ConvertThisArray_";

+

+        if(level > 1){

+         

+          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>"

+          const tmpCmp = Component({template: textAreaTemplate})(class {

+          });

+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {

+          });

+          

+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();

+          

+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)

+            .then((factories) => {

+              const f = factories.componentFactories[0];

+              const cmpRef = this.containerDiv.createComponent(f);

+              cmpRef.instance.form = this.form;

+            })

+        }

+        else{

+          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>"

+          const tmpCmp = Component({template: textAreaTemplate})(class {

+          });

+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {

+          });

+          

+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();

+          

+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)

+            .then((factories) => {

+              const f = factories.componentFactories[0];

+              const cmpRef = this.containerDiv.createComponent(f);

+              cmpRef.instance.form = this.form;

+            })

+        }

+        // 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>"

+        // const tmpCmp = Component({template: textAreaTemplate})(class {

+        // });

+        // const tmpModule = NgModule({imports:[ReactiveFormsModule] ,declarations: [tmpCmp]})(class {

+        // });

+        

+        // //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();

+        

+        // this.compiler.compileModuleAndAllComponentsAsync(tmpModule)

+        //   .then((factories) => {

+        //     const f = factories.componentFactories[0];

+        //     const cmpRef = this.containerDiv.createComponent(f);

+        //     cmpRef.instance.form = this.form;

+        //   })

+        

+      }

+      else if(typeof data[key] === 'boolean'){

+        let str = '';

+        let str2 = 'h5';

+        let bold = ' style="font-weight:bold"'

+        if(level > 1){

+          str2 = 'label';

+          bold = '';

+        }

+        if(data[key]){

+          str = '<option [ngValue]="true">true</option><option [ngValue]="false">false</option>';

+        }else{

+          str = '<option [ngValue]="false">false</option><option [ngValue]="true">true</option>';

+        }

+        this.form.addControl(keyPath + key.trim(), new FormControl(data[key]));

+        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>"

+          const tmpCmp = Component({template: textAreaTemplate})(class {

+        });

+        const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {

+        });

+        

+        //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();

+        

+        this.compiler.compileModuleAndAllComponentsAsync(tmpModule)

+          .then((factories) => {

+            const f = factories.componentFactories[0];

+            const cmpRef = this.containerDiv.createComponent(f);

+            cmpRef.instance.form = this.form;

+          })

+      }

+      else if(typeof data[key] === typeof 23){

+        let str = 'h5';

+        let bold = ' style="font-weight:bold"';

+        if(level > 1){

+          str = 'label';

+          bold = '';

+        }

+        this.form.addControl(keyPath + key.trim(), new FormControl(data[key]));

+        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>';

+          const tmpCmp = Component({template: textInputTemplate})(class {

+        });

+        const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {

+        });

+        

+        //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();

+        

+        this.compiler.compileModuleAndAllComponentsAsync(tmpModule)

+          .then((factories) => {

+            const f = factories.componentFactories[0];

+            const cmpRef = this.containerDiv.createComponent(f);

+            cmpRef.instance.form = this.form;

+          })

+      }

+      else{

+        const textAreaTemplate = ' <h5 style="font-weight:bold" class="mr-2 '+ indent + '">' + key.trim() + ': Type Not Supported</h5>';// + path + "'> "+ data[key] + "</textarea>"

+          const tmpCmp = Component({template: textAreaTemplate})(class {

+          });

+         const tmpModule = NgModule({imports:[ReactiveFormsModule], declarations: [tmpCmp]})(class {

+          });

+          

+          //this.containerDiv.element.nativeElement.appendChild(document.createElement("label")).innerHTML = key.trim();

+          

+          this.compiler.compileModuleAndAllComponentsAsync(tmpModule)

+            .then((factories) => {

+              const f = factories.componentFactories[0];

+              const cmpRef = this.containerDiv.createComponent(f);

+              cmpRef.instance.form = this.form;

+            })

+      }

+

+    }

+  }

+}

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
index 0000000..4fe9215
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { FormGeneratorModule } from './form-generator.module';

+

+describe('FormGeneratorModule', () => {

+  let formGeneratorModule: FormGeneratorModule;

+

+  beforeEach(() => {

+    formGeneratorModule = new FormGeneratorModule();

+  });

+

+  it('should create an instance', () => {

+    expect(formGeneratorModule).toBeTruthy();

+  });

+});

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
index 0000000..6d418cd
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/form-generator.module.ts
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { FormGeneratorComponent } from './form-generator.component';

+import { MatButtonModule, MatSnackBarModule } from '@angular/material';

+import { TextAreaComponent } from './text-area/text-area.component';

+import { FormsModule, ReactiveFormsModule } from '@angular/forms';

+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    MatButtonModule,

+    FormsModule,

+    ReactiveFormsModule,

+    MatSnackBarModule,

+    AlertSnackbarModule

+  ],

+  declarations: [ FormGeneratorComponent, TextAreaComponent ],

+  exports: [ FormGeneratorComponent ]

+})

+export class FormGeneratorModule { }

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
index 0000000..5c3c3aa
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.html
@@ -0,0 +1,17 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<textarea [(value)]="textValue"></textarea>

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..3684549
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TextAreaComponent } from './text-area.component';

+

+describe('TextAreaComponent', () => {

+  let component: TextAreaComponent;

+  let fixture: ComponentFixture<TextAreaComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TextAreaComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TextAreaComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..d144d17
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/form-generator/text-area/text-area.component.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+

+@Component({

+  selector: 'app-text-area',

+  templateUrl: './text-area.component.html',

+  styleUrls: ['./text-area.component.scss']

+})

+export class TextAreaComponent implements OnInit {

+  public textValue = "hello";

+  constructor() { }

+

+  ngOnInit() {

+  }

+

+}

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
index 0000000..97d2c90
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/index.ts
@@ -0,0 +1,18 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+export * from './page-header/page-header.module';

+// export * from './stat/stat.module';

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
index 0000000..9fb2178
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.pug
@@ -0,0 +1,18 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+p onboard-mechid works!

+

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..5c404d8
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { OnboardMechidComponent } from './onboard-mechid.component';

+

+describe('OnboardMechidComponent', () => {

+  let component: OnboardMechidComponent;

+  let fixture: ComponentFixture<OnboardMechidComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ OnboardMechidComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(OnboardMechidComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..1f61f0a
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.component.ts
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject } from '@angular/core';

+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';

+

+@Component({

+  selector: 'app-onboard-mechid',

+  templateUrl: './onboard-mechid.component.pug',

+  styleUrls: ['./onboard-mechid.component.scss']

+})

+export class OnboardMechidComponent implements OnInit {

+

+  constructor(

+    public dialogRef: MatDialogRef<OnboardMechidComponent>,

+    @Inject(MAT_DIALOG_DATA) public input_data

+    ) { 

+  

+  }

+

+  ngOnInit() {

+  }

+

+  onboardMechid(){

+    //call rohans api endpoint

+    //save mechId as a user

+  }

+

+}

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
index 0000000..32d6392
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { OnboardMechidModule } from './onboard-mechid.module';

+

+describe('OnboardMechidModule', () => {

+  let onboardMechidModule: OnboardMechidModule;

+

+  beforeEach(() => {

+    onboardMechidModule = new OnboardMechidModule();

+  });

+

+  it('should create an instance', () => {

+    expect(onboardMechidModule).toBeTruthy();

+  });

+});

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
index 0000000..1a44865
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/onboard-mechid/onboard-mechid.module.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { OnboardMechidComponent } from './onboard-mechid.component';

+

+@NgModule({

+  imports: [

+    CommonModule

+  ],

+  declarations: [OnboardMechidComponent],

+  exports: [OnboardMechidComponent],

+  entryComponents: [OnboardMechidComponent]

+})

+export class OnboardMechidModule { }

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
index 0000000..baa008a
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.html
@@ -0,0 +1,31 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<div class="row">

+    <div class="col-xl-12">

+        <h2 class="page-header">

+            {{heading}}

+        </h2>

+        <!-- Breadcumbs

+        <ol class="breadcrumb">

+            <li class="breadcrumb-item">

+                <i class="fa fa-dashboard"></i> <a href="Javascript:void(0)" [routerLink]="['/dashboard']">Dashboard</a>

+            </li>

+            <li class="breadcrumb-item active"><i class="fa {{icon}}"></i> {{heading}}</li>

+        </ol>

+        -->

+    </div>

+</div>

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..d713226
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.spec.ts
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing'

+import { RouterTestingModule } from '@angular/router/testing'

+

+import { PageHeaderComponent } from './page-header.component'

+import { PageHeaderModule } from './page-header.module'

+

+describe('PageHeaderComponent', () => {

+  let component: PageHeaderComponent

+  let fixture: ComponentFixture<PageHeaderComponent>

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      imports: [PageHeaderModule, RouterTestingModule],

+    })

+    .compileComponents()

+  }))

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(PageHeaderComponent)

+    component = fixture.componentInstance

+    fixture.detectChanges()

+  })

+

+  it('should create', () => {

+    expect(component).toBeTruthy()

+  })

+})

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
index 0000000..5c4b5bf
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/page-header/page-header.component.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input } from '@angular/core';

+import { RouterModule } from '@angular/router';

+

+@Component({

+    selector: 'app-page-header',

+    templateUrl: './page-header.component.html',

+    styleUrls: ['./page-header.component.scss']

+})

+export class PageHeaderComponent implements OnInit {

+    @Input() heading: string;

+    @Input() icon: string;

+    constructor() {}

+

+    ngOnInit() {}

+}

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
index 0000000..c76516b
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/page-header/page-header.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { PageHeaderModule } from './page-header.module';

+

+describe('PageHeaderModule', () => {

+  let pageHeaderModule: PageHeaderModule;

+

+  beforeEach(() => {

+    pageHeaderModule = new PageHeaderModule();

+  });

+

+  it('should create an instance', () => {

+    expect(pageHeaderModule).toBeTruthy();

+  });

+});

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
index 0000000..e0d2d7b
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/page-header/page-header.module.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { RouterModule } from '@angular/router';

+

+import { PageHeaderComponent } from './page-header.component';

+

+@NgModule({

+    imports: [CommonModule, RouterModule],

+    declarations: [PageHeaderComponent],

+    exports: [PageHeaderComponent]

+})

+export class PageHeaderModule {}

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
index 0000000..72b7884
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.pug
@@ -0,0 +1,72 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+h2.mb-1(mat-dialog-title) Schedule {{selectedTestInstance ? selectedTestInstance.testInstanceName : ''}}

+

+mat-dialog-content(*ngIf="selectedTestInstance")

+  .row

+    .col-sm-6

+      h5 Create Schedule

+      .row

+        .col-sm-6

+          mat-form-field

+            input(matInput, [matDatepicker]="schedulePicker", [(ngModel)]='startDate', placeholder="Select Start Date", required)

+            mat-datepicker-toggle(matSuffix, [for]="schedulePicker")

+            mat-datepicker(#schedulePicker)

+        .col-sm-6

+          mat-form-field

+            input(matInput, [(ngModel)]="timeToRun", [ngxTimepicker]="picker", placeholder="Select Time", required)

+            ngx-material-timepicker(#picker)

+

+      .row.mb-2

+        .col-12

+          mat-slide-toggle(color="primary", [(ngModel)]="frequency") Add Frequency

+

+      .row(*ngIf="frequency").mb-2

+        .col-sm-12

+          mat-form-field.mr-2

+            input(matInput, type="number",  [(ngModel)]='numUnit', placeholder='Execution Interval', required)

+          mat-form-field

+            mat-select(placeholder='Time Unit', [(ngModel)]='timeUnit', required)

+              mat-option([value]=60) min(s)

+              mat-option([value]=3600) hour(s)

+              mat-option([value]=86400) day(s)

+        .col-sm-6

+          mat-form-field

+            input(matInput, [matDatepicker]="schedulePicker2",  [(ngModel)]='endDate', placeholder="Select a End Date (Optional)")

+            mat-datepicker-toggle(matSuffix, [for]="schedulePicker2")

+            mat-datepicker(#schedulePicker2)

+      

+      .row

+        .col-12

+          button(mat-raised-button, color="primary", (click)='createSchedule()') Create Schedule

+

+    .col-sm-6

+      h5 Scheduled Runs

+      .row(*ngIf="scheduledJobs")

+        .col-12

+          .group-list

+            .group-list-item(*ngFor="let job of scheduledJobs") 

+              a((click)="deleteJob(job)") 

+                i.fa.fa-times 

+              |  {{ job.data.testSchedule._testInstanceStartDate }} {{job.data.testSchedule._testInstanceEndDate ? 'to ' + job.data.testSchedule._testInstanceEndDate : '' }} {{ job.data.testSchedule._testInstanceExecFreqInSeconds ? 'every ' + job.data.testSchedule._testInstanceExecFreqInSeconds + ' sec' : '' }}

+            .group-list-item(*ngIf="!loadingJobs && scheduledJobs.length == 0", style="text-align:center") Nothing is scheduled

+            .group-list-item(*ngIf="loadingJobs")

+              mat-spinner(style="margin:auto")

+mat-dialog-actions.pull-right

+  button.pull-right(mat-button, mat-dialog-close) Close

+    // The mat-dialog-close directive optionally accepts a value as a result for the dialog.

+    

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..68e0f26
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { ScheduleTestModalComponent } from './schedule-test-modal.component';

+

+describe('ScheduleTestModalComponent', () => {

+  let component: ScheduleTestModalComponent;

+  let fixture: ComponentFixture<ScheduleTestModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ ScheduleTestModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(ScheduleTestModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..e87e6dd
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.component.ts
@@ -0,0 +1,190 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, Inject, OnInit } from '@angular/core';

+import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatSnackBar } from '@angular/material';

+import { TestInstanceService } from '../../services/test-instance.service';

+import { SchedulingService } from '../../services/scheduling.service';

+import { AlertSnackbarComponent } from '../alert-snackbar/alert-snackbar.component';

+import { AlertModalComponent } from '../alert-modal/alert-modal.component';

+

+

+@Component({

+  selector: 'app-schedule-test-modal',

+  templateUrl: './schedule-test-modal.component.pug',

+  styleUrls: ['./schedule-test-modal.component.scss']

+})

+export class ScheduleTestModalComponent implements OnInit {

+

+  public data;

+  public test_instances;

+  public selectedTestInstance;

+  public schedule;

+  public search;

+  public timeUnit;

+  public timeToRun;

+  public numUnit;

+  public startDate;

+  public endDate;

+  public frequency = false;

+  public isSelected = false;

+  public scheduledJobs;

+  public loadingJobs;

+

+  constructor(

+    private schedulingService: SchedulingService,

+    private testInstanceService: TestInstanceService,

+    public dialogRef: MatDialogRef<ScheduleTestModalComponent>,

+    private snack: MatSnackBar,

+    private dialog: MatDialog,

+    @Inject(MAT_DIALOG_DATA) public input_data

+  ) {

+

+  }

+

+  onNoClick(): void {

+    this.dialogRef.close();

+  }

+

+  ngOnInit() {

+    this.timeUnit = 60;

+    this.numUnit = 0;

+    this.search = {};

+    this.selectedTestInstance = {};

+    this.startDate = null;

+    this.timeToRun = null;

+    this.endDate = null;

+    //this.search.testInstanceName = ""; 

+    //this.test_instances = [];

+    this.schedule = {};

+    this.schedule.testInstanceExecFreqInSeconds = '';

+    this.scheduledJobs = [];

+    this.loadingJobs = true;

+

+    //console.log(this.test_instances);

+    this.testInstanceService.get(this.input_data.id).subscribe(

+      result => {

+        this.selectedTestInstance = result;

+      }

+    );

+

+    this.schedulingService.find({$limit: -1, testInstanceId: this.input_data.id}).subscribe(

+      result => {

+        for (let i = 0; i < result['length']; i++) {

+          result[i].data.testSchedule._testInstanceStartDate = new Date(result[i].data.testSchedule._testInstanceStartDate).toLocaleString();

+          if (result[i].data.testSchedule._testInstanceEndDate) {

+            result[i].data.testSchedule._testInstanceEndDate = new Date(result[i].data.testSchedule._testInstanceEndDate).toLocaleString();

+          }

+          this.scheduledJobs.push(result[i]);

+

+        }

+        this.loadingJobs = false;

+      }

+    );

+  }

+

+  convertDate(date, time = ''): Date {

+    let nDate = new Date(date + '');

+    return new Date(nDate.getMonth() + 1 + '/' + nDate.getDate() + '/' + nDate.getFullYear() + ' ' + time);

+  }

+

+  createSchedule() {

+    this.convertDate(this.startDate, this.timeToRun);

+

+    if (!this.selectedTestInstance || !this.startDate || !this.timeToRun) {

+      this.dialog.open(AlertModalComponent, {

+        width: '450px',

+        data: {

+          type: 'Alert',

+          message: 'Select start date/time before you create schedule!'

+        }

+      });

+      return;

+    }

+    if (this.frequency) {

+      this.schedule = {

+        testInstanceId: this.selectedTestInstance._id,

+        testInstanceStartDate: this.convertDate(this.startDate, this.timeToRun).toISOString(),

+        testInstanceExecFreqInSeconds: this.numUnit * this.timeUnit,

+        async: false,

+        asyncTopic: ''

+      };

+      

+

+      if(this.endDate){

+        this.schedule.testInstanceEndDate = this.convertDate(this.endDate).toISOString();

+      }

+    } else {

+      this.schedule = {

+        testInstanceId: this.selectedTestInstance._id,

+        testInstanceStartDate: this.convertDate(this.startDate, this.timeToRun).toISOString(),

+        async: false,

+        asyncTopic: ''

+      };

+      //console.log(this.schedule);

+      

+    }

+

+    this.schedulingService.create(this.schedule).subscribe((result) => {

+      this.snack.openFromComponent(AlertSnackbarComponent, {

+        duration: 1500,

+        data: {

+          message: 'Schedule Created!'

+        }

+      });

+      this.ngOnInit();

+    }, err => {

+      this.dialog.open(AlertModalComponent, {

+        data: {

+          type: "alert", 

+          message: err.message

+        }

+      })

+    })

+    // console.log(this.schedule);

+  }

+

+  deleteJob(job) {

+    var deleteJob = this.dialog.open(AlertModalComponent, {

+      width: '250px',

+      data: {

+        type: 'confirmation',

+        message: 'Are you sure you want to delete this schedule?'

+      }

+    });

+

+    deleteJob.afterClosed().subscribe(

+      result => {

+        if (result) {

+          this.schedulingService.delete(job._id).subscribe(

+            result => {

+              this.ngOnInit();

+            }

+          );

+        }

+      }

+    );

+  }

+  // this.testInstanceId = testInstanceId;

+  // this.testInstanceStartDate = testInstanceStartDate;

+  // this.testInstanceExecFreqInSeconds = testInstanceExecFreqInSeconds;

+  // this.testInstanceEndDate = testInstanceEndDate;

+  // this.async = async;

+  // this.asyncTopic = asyncTopic;

+  // this.executorId = executorId;

+

+

+}

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
index 0000000..7ce918e
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { ScheduleTestModalModule } from './schedule-test-modal.module';

+

+describe('ScheduleTestModalModule', () => {

+  let scheduleTestModalModule: ScheduleTestModalModule;

+

+  beforeEach(() => {

+    scheduleTestModalModule = new ScheduleTestModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(scheduleTestModalModule).toBeTruthy();

+  });

+});

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
index 0000000..ed9a26d
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/schedule-test-modal/schedule-test-modal.module.ts
@@ -0,0 +1,55 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+//needed imports for Material Dialogue

+import { MatDialogModule, MatRadioModule, MatIconModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatDatepickerModule, MatNativeDateModule, MatCheckboxModule, MatSelectModule, MatSnackBarModule, MatSlideToggleModule, MatProgressSpinnerModule} from '@angular/material';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { FormsModule } from '@angular/forms';

+import { ScheduleTestModalComponent } from './schedule-test-modal.component';

+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';

+import { AlertModalModule } from '../alert-modal/alert-modal.module';

+import {NgxMaterialTimepickerModule} from 'ngx-material-timepicker';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FormsModule,

+    FilterPipeModule,

+    MatButtonModule,

+    MatInputModule,

+    MatRadioModule,

+    MatDialogModule,

+    MatDatepickerModule,

+    MatSnackBarModule,

+    AlertSnackbarModule,

+    AlertModalModule,

+    MatSnackBarModule,

+    MatSelectModule,

+    MatNativeDateModule,

+    MatCheckboxModule,

+    MatIconModule,

+    NgxMaterialTimepickerModule.forRoot(),

+    MatFormFieldModule,

+    MatSlideToggleModule,

+    MatProgressSpinnerModule

+  ],

+  declarations: [ScheduleTestModalComponent ],

+  exports: [ScheduleTestModalComponent],

+  entryComponents: [ScheduleTestModalComponent]

+})

+export class ScheduleTestModalModule { }

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
index 0000000..9785f93
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.pug
@@ -0,0 +1,35 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+h2.mb-1(mat-dialog-title) Select Test Definition

+//input.bg-light.form-control(mat-dialog-title, type="text", placeholder="Search...", [(ngModel)]="search.testName")

+mat-form-field(style="width:100%")

+  input(matInput, type='search', placeholder='Search...', color='blue', [(ngModel)]='search.testName')

+  button(mat-button, *ngIf='search.testName', matSuffix, mat-icon-button, aria-label='Clear', (click)="search.testName=''")

+    mat-icon close

+h5([hidden]='test_definitions.length != 0') No Test Definitions found.

+mat-dialog-content

+  .list-group

+    mat-radio-group([(ngModel)]="input_data.testDefinition")

+      .list-group-item(*ngFor="let testDefinition of test_definitions | filterBy:search")

+        mat-radio-button([value]="testDefinition")

+          .ml-2

+            h5 {{ testDefinition.testName }} 

+            p.mb-0 {{ testDefinition.testDescription}}

+mat-dialog-actions

+    button(mat-button, mat-dialog-close) Cancel 

+    // The mat-dialog-close directive optionally accepts a value as a result for the dialog.

+    button.bg-primary.text-white(mat-button, [mat-dialog-close]='input_data.testDefinition') Select

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..b318dad
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { SelectStrategyModalComponent } from './select-strategy-modal.component';

+

+describe('SelectStrategyModalComponent', () => {

+  let component: SelectStrategyModalComponent;

+  let fixture: ComponentFixture<SelectStrategyModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ SelectStrategyModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(SelectStrategyModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..371c8ae
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.component.ts
@@ -0,0 +1,65 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, Inject, OnInit } from '@angular/core';

+import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';

+import { TestDefinitionService } from '../../services/test-definition.service';

+import { GroupService } from 'app/shared/services/group.service';

+

+@Component({

+  selector: 'app-select-strategy-modal',

+  templateUrl: './select-strategy-modal.component.pug',

+  styleUrls: ['./select-strategy-modal.component.scss']

+})

+export class SelectStrategyModalComponent implements OnInit  {

+

+  public data; 

+  public test_definitions; 

+  public search;

+

+  constructor(

+    public dialogRef: MatDialogRef<SelectStrategyModalComponent>,

+    private testDefinitionService: TestDefinitionService, 

+    private _groups: GroupService,

+    @Inject(MAT_DIALOG_DATA) public input_data

+  ) {

+    this.data = {};

+   }

+  

+  onNoClick(): void {

+    this.dialogRef.close();

+  }

+

+  ngOnInit() {

+    this.test_definitions = [];

+    let groupId = this._groups.getGroup()['_id'];

+    

+    this.testDefinitionService.find({$limit: -1, groupId: groupId, disabled: { $ne: true }, 'bpmnInstances.isDeployed': true, $populate: ['bpmnInstances.testHeads.testHeadId'] })

+      .subscribe(

+        (result) => {

+            this.test_definitions = result;

+        },

+        (error) => {

+            console.log(error);

+      });

+

+    

+    this.search = {};

+    this.search.testName = ""; 

+    this.input_data.testDefinition = {};

+  }

+

+}

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
index 0000000..edebb83
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { SelectStrategyModalModule } from './select-strategy-modal.module';

+

+describe('SelectStrategyModalModule', () => {

+  let selectStrategyModalModule: SelectStrategyModalModule;

+

+  beforeEach(() => {

+    selectStrategyModalModule = new SelectStrategyModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(selectStrategyModalModule).toBeTruthy();

+  });

+});

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
index 0000000..fca9f67
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-strategy-modal/select-strategy-modal.module.ts
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { SelectStrategyModalComponent } from './select-strategy-modal.component';

+import { MatDialogModule, MatRadioModule, MatIconModule, MatFormFieldModule, MatInputModule, MatButtonModule } from '@angular/material';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { FormsModule } from '@angular/forms';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FormsModule,

+    FilterPipeModule,

+    MatButtonModule,

+    MatInputModule,

+    MatRadioModule,

+    MatDialogModule,

+    MatIconModule,

+    MatFormFieldModule

+  ],

+  declarations: [SelectStrategyModalComponent],

+  exports: [SelectStrategyModalComponent],

+  entryComponents: [SelectStrategyModalComponent]

+})

+export class SelectStrategyModalModule { }

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
index 0000000..da2d0c0
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.pug
@@ -0,0 +1,30 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+h2(mat-dialog-title) Select Test Head

+input.bg-light.form-control(mat-dialog-title, type="text", placeholder="Search...", [(ngModel)]="search.testHeadName")

+mat-dialog-content

+  .list-group

+    mat-radio-group([(ngModel)]="input_data.testHead")

+      .list-group-item(*ngFor="let testHead of test_heads | filterBy:search")

+        mat-radio-button([value]="testHead")

+          .ml-2

+            h5 {{ testHead.testHeadName }}

+            p.mb-0 {{ testHead.testHeadDescription }}

+mat-dialog-actions

+    button(mat-button, mat-dialog-close) Cancel

+    // The mat-dialog-close directive optionally accepts a value as a result for the dialog.

+    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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..b15d850
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { SelectTestHeadModalComponent } from './select-test-head-modal.component';

+

+describe('SelectTestHeadModalComponent', () => {

+  let component: SelectTestHeadModalComponent;

+  let fixture: ComponentFixture<SelectTestHeadModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ SelectTestHeadModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(SelectTestHeadModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..9c81b59
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.component.ts
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject } from '@angular/core';

+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';

+import { TestHeadService } from '../../services/test-head.service';

+

+@Component({

+  selector: 'app-select-test-head-modal',

+  templateUrl: './select-test-head-modal.component.pug',

+  styleUrls: ['./select-test-head-modal.component.scss']

+})

+export class SelectTestHeadModalComponent implements OnInit {

+

+  public data = {test_heads: []};

+  public test_heads;

+  public search;

+  public selected;

+

+  constructor(public dialogRef: MatDialogRef<SelectTestHeadModalComponent>,

+    private testHeadService: TestHeadService,

+    @Inject(MAT_DIALOG_DATA) public input_data

+  ) { }

+

+  ngOnInit() {

+    this.search = {};

+    this.input_data.testHead = {};

+    this.test_heads = [{}];

+    this.testHeadService.find({$limit: -1})

+      .subscribe(

+        (result) => {

+            this.test_heads = result;

+        },

+        (error) => {

+            alert(error.error.message);

+      });

+    //console.log(this.test_heads)

+  }

+

+}

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
index 0000000..ed2b5df
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { SelectTestHeadModalModule } from './select-test-head-modal.module';

+

+describe('SelectTestHeadModalModule', () => {

+  let selectTestHeadModalModule: SelectTestHeadModalModule;

+

+  beforeEach(() => {

+    selectTestHeadModalModule = new SelectTestHeadModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(selectTestHeadModalModule).toBeTruthy();

+  });

+});

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
index 0000000..7a11ef6
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/select-test-head-modal/select-test-head-modal.module.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { SelectTestHeadModalComponent } from './select-test-head-modal.component';

+import { MAT_DIALOG_DEFAULT_OPTIONS, MatRadioModule, MatDialogModule, MatInputModule, MatButtonModule, MatFormFieldModule, MatIconModule } from '@angular/material';

+import { FormsModule } from '@angular/forms';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FormsModule,

+    FilterPipeModule,

+    MatButtonModule,

+    MatInputModule,

+    MatRadioModule,

+    MatDialogModule,

+    MatIconModule,

+    MatFormFieldModule

+  ],

+  declarations: [SelectTestHeadModalComponent],

+  exports: [SelectTestHeadModalComponent],

+  providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}],

+  entryComponents: [SelectTestHeadModalComponent]

+})

+export class SelectTestHeadModalModule { }

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
index 0000000..02cd2ba
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/stat/stat.component.html
@@ -0,0 +1,35 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<div class="card text-white bg-{{bgClass}}">

+    <div class="card-header">

+        <div class="row">

+            <div class="col col-xs-3">

+                <i class="fa {{icon}} fa-5x"></i>

+            </div>

+            <div class="col col-xs-9 text-right">

+                <div class="d-block huge">{{count}}</div>

+                <div class="d-block">{{label}}</div>

+            </div>

+        </div>

+    </div>

+    <div class="card-footer">

+        <span class="float-left">View Details {{data}}</span>

+        <a href="javascript:void(0)" class="float-right card-inverse">

+            <span ><i class="fa fa-arrow-circle-right"></i></span>

+        </a>

+    </div>

+</div>

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/stat/stat.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..903c861
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/stat/stat.component.spec.ts
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { StatComponent } from './stat.component';

+

+describe('StatComponent', () => {

+    let component: StatComponent;

+    let fixture: ComponentFixture<StatComponent>;

+

+    beforeEach(

+        async(() => {

+            TestBed.configureTestingModule({

+                declarations: [StatComponent]

+            }).compileComponents();

+        })

+    );

+

+    beforeEach(() => {

+        fixture = TestBed.createComponent(StatComponent);

+        component = fixture.componentInstance;

+        fixture.detectChanges();

+    });

+

+    it('should create', () => {

+        expect(component).toBeTruthy();

+    });

+});

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
index 0000000..d5ff3e4
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/stat/stat.component.ts
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

+

+@Component({

+    selector: 'app-stat',

+    templateUrl: './stat.component.html',

+    styleUrls: ['./stat.component.scss']

+})

+export class StatComponent implements OnInit {

+    @Input() bgClass: string;

+    @Input() icon: string;

+    @Input() count: number;

+    @Input() label: string;

+    @Input() data: number;

+    @Output() event: EventEmitter<any> = new EventEmitter();

+

+    constructor() {}

+

+    ngOnInit() {}

+}

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
index 0000000..b089e89
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/stat/stat.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { StatModule } from './stat.module';

+

+describe('StatModule', () => {

+    let statModule: StatModule;

+

+    beforeEach(() => {

+        statModule = new StatModule();

+    });

+

+    it('should create an instance', () => {

+        expect(statModule).toBeTruthy();

+    });

+});

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
index 0000000..211413a
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/stat/stat.module.ts
@@ -0,0 +1,26 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { StatComponent } from './stat.component';

+

+@NgModule({

+    imports: [CommonModule],

+    declarations: [StatComponent],

+    exports: [StatComponent]

+})

+export class StatModule {}

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
index 0000000..5edf087
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.pug
@@ -0,0 +1,25 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div(style="position:relative") 

+  .row()

+    .col-12

+      h2.pull-left(mat-dialog-title) Test Definition

+      button.pull-right(mat-icon-button, (click)="close()")

+        mat-icon close

+  .row

+    mat-dialog-content

+      app-create-test-form(*ngIf="formData", [formData]="formData", (childEvent)="close($event)")

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..d932d95
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestDefinitionModalComponent } from './test-definition-modal.component';

+

+describe('TestDefinitionModalComponent', () => {

+  let component: TestDefinitionModalComponent;

+  let fixture: ComponentFixture<TestDefinitionModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestDefinitionModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestDefinitionModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..3e681ed
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.component.ts
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject, Output, Input } from '@angular/core';

+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

+import { AppGlobals } from 'app/app.global';

+import { HttpClient } from '@angular/common/http';

+import { TestDefinitionService } from 'app/shared/services/test-definition.service';

+

+@Component({

+  selector: 'app-test-definition-modal',

+  templateUrl: './test-definition-modal.component.pug',

+  styleUrls: ['./test-definition-modal.component.scss']

+})

+export class TestDefinitionModalComponent implements OnInit {

+

+  @Output() formData;

+

+  @Input() childEvent;

+

+  constructor(

+      public dialogRef: MatDialogRef<TestDefinitionModalComponent>,

+      private http: HttpClient,

+      private testDefinition: TestDefinitionService,

+    @Inject(MAT_DIALOG_DATA) public input_data) { }

+

+  ngOnInit() {

+    if (this.input_data.testDefinitionId) {

+      this.testDefinition.get(this.input_data.testDefinitionId).subscribe(result => {

+        this.formData = result;

+      });

+    } else {

+      this.formData = 'new';

+    }

+  }

+

+  close() {

+    this.dialogRef.close();

+  }

+}

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
index 0000000..245e8dc
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestDefinitionModalModule } from './test-definition-modal.module';

+

+describe('TestDefinitionModalModule', () => {

+  let testDefinitionModalModule: TestDefinitionModalModule;

+

+  beforeEach(() => {

+    testDefinitionModalModule = new TestDefinitionModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(testDefinitionModalModule).toBeTruthy();

+  });

+});

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
index 0000000..d136b4b
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-definition-modal/test-definition-modal.module.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { TestDefinitionModalComponent } from './test-definition-modal.component';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { MatButtonModule, MatInputModule, MatRadioModule, MatDialogModule, MatIconModule, MatFormFieldModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material';

+import { CreateTestFormModule } from '../create-test-form/create-test-form.module';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FilterPipeModule,

+    MatButtonModule,

+    MatInputModule,

+    MatRadioModule,

+    MatDialogModule,

+    MatIconModule,

+    MatFormFieldModule,

+    CreateTestFormModule

+  ],

+  declarations: [TestDefinitionModalComponent],

+  exports: [TestDefinitionModalComponent],

+  entryComponents: [TestDefinitionModalComponent],

+  providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}]

+})

+export class TestDefinitionModalModule { }

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
index 0000000..13423ed
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.pug
@@ -0,0 +1,25 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div(style="position:relative") 

+  .row()

+    .col-12

+      h2.pull-left(mat-dialog-title) Test Head

+      button.pull-right(mat-icon-button, (click)="close()")

+        mat-icon close

+  .row

+    mat-dialog-content(style="width: 100%")

+      app-create-test-head-form(*ngIf="formData", [options]="formOptions", [formData]="formData", (childEvent)="close($event)")

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..c4523c5
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestHeadModalComponent } from './test-head-modal.component';

+

+describe('TestHeadModalComponent', () => {

+  let component: TestHeadModalComponent;

+  let fixture: ComponentFixture<TestHeadModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestHeadModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestHeadModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..eed1766
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.component.ts
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject, Output, Input } from '@angular/core';

+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';

+import { AppGlobals } from '../../../app.global';

+import { HttpClient } from '@angular/common/http';

+

+@Component({

+  selector: 'app-test-head-modal',

+  templateUrl: './test-head-modal.component.pug',

+  styleUrls: ['./test-head-modal.component.scss']

+})

+export class TestHeadModalComponent implements OnInit {

+

+  public formOptions;

+

+  public formData;

+

+  @Input() childEvent;

+

+  constructor(public dialogRef: MatDialogRef<TestHeadModalComponent>, private http: HttpClient,

+    @Inject(MAT_DIALOG_DATA) public input_data

+  ) { }

+

+  ngOnInit() {

+    this.formOptions = {

+      goal: this.input_data.goal

+    };

+    if(this.input_data.testHead)

+      this.formData = this.input_data.testHead;

+    else

+      this.formData = {};

+  }

+

+  close(){

+    this.dialogRef.close();

+  }

+

+}

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
index 0000000..fd16ca5
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestHeadModalModule } from './test-head-modal.module';

+

+describe('TestHeadModalModule', () => {

+  let testHeadModalModule: TestHeadModalModule;

+

+  beforeEach(() => {

+    testHeadModalModule = new TestHeadModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(testHeadModalModule).toBeTruthy();

+  });

+});

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
index 0000000..66a214a
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-head-modal/test-head-modal.module.ts
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { TestHeadModalComponent } from './test-head-modal.component';

+import { FormsModule } from '@angular/forms';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { MatButtonModule, MatInputModule, MatRadioModule, MatDialogModule, MatFormFieldModule, MatIconModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material';

+import { CreateTestHeadFormModule } from '../create-test-head-form/create-test-head-form.module';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FormsModule,

+    FilterPipeModule,

+    MatButtonModule,

+    MatInputModule,

+    MatRadioModule,

+    MatDialogModule,

+    MatIconModule,

+    MatFormFieldModule,

+    CreateTestHeadFormModule

+  ],

+  declarations: [TestHeadModalComponent],

+  exports: [TestHeadModalComponent],

+  entryComponents: [TestHeadModalComponent],

+  providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}]

+})

+export class TestHeadModalModule { }

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
index 0000000..7cc39e0
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.pug
@@ -0,0 +1,25 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+div(style="position:relative") 

+  .row()

+    .col-12

+      h2.pull-left(mat-dialog-title) Test Instance

+      button.pull-right(mat-icon-button, (click)="close()")

+        mat-icon close

+  .row

+    mat-dialog-content(style="width:100%")

+      app-create-test-instance-form(*ngIf='!findInstance', [existingInstance] = 'editInstance', (childEvent)="close($event)")

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..703bfcb
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { TestInstanceModalComponent } from './test-instance-modal.component';

+

+describe('TestInstanceModalComponent', () => {

+  let component: TestInstanceModalComponent;

+  let fixture: ComponentFixture<TestInstanceModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ TestInstanceModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(TestInstanceModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..9fb4f43
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.component.ts
@@ -0,0 +1,80 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject, Output, Input } from '@angular/core';

+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

+import { AppGlobals } from 'app/app.global';

+import { HttpClient } from '@angular/common/http';

+import { TestInstanceService } from '../../services/test-instance.service';

+import { TestDefinitionService } from '../../services/test-definition.service';

+

+@Component({

+  selector: 'app-test-instance-modal',

+  templateUrl: './test-instance-modal.component.pug',

+  styleUrls: ['./test-instance-modal.component.scss']

+})

+export class TestInstanceModalComponent implements OnInit {

+

+  @Output() editInstance;

+  public findInstance = true;

+  @Input() childEvent;

+

+

+  constructor(

+      public dialogRef: MatDialogRef<TestInstanceModalComponent>,

+      private http: HttpClient,

+      private testDefintionService: TestDefinitionService,

+      private testInstanceService: TestInstanceService,

+    @Inject(MAT_DIALOG_DATA) public inputInstanceId) { }

+

+  ngOnInit() {

+    if(!this.inputInstanceId){

+      this.findInstance = false;

+    }

+    //if the user is creating an Instance from a test definition page. Pull all data and populate testHeads

+    else if(this.inputInstanceId["td"]){

+      this.testDefintionService.get(this.inputInstanceId.td,{$populate: ['bpmnInstances.testHeads.testHeadId']}).subscribe((result) => {

+        this.editInstance = {

+          

+          testDefinition: result,

+          isEdit: false

+        };

+        

+        this.findInstance = false;

+      });

+

+    }

+    else if (this.inputInstanceId["ti"]) {

+      this.testInstanceService.get(this.inputInstanceId.ti, {$populate: ['testDefinitionId']}).subscribe((result) => {

+        

+        this.editInstance = {};

+        this.editInstance.testInstance = result;

+        if(this.inputInstanceId.isEdit){

+          this.editInstance.isEdit = true;

+        }else{

+          this.editInstance.isEdit = false;

+        }

+        this.findInstance = false;

+      });

+    }else{

+      this.findInstance = false

+    }

+  }

+

+  close() {

+    this.dialogRef.close();

+  }

+}
\ 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
index 0000000..8ca1097
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestInstanceModalModule } from './test-instance-modal.module';

+

+describe('TestInstanceModalModule', () => {

+  let testInstanceModalModule: TestInstanceModalModule;

+

+  beforeEach(() => {

+    testInstanceModalModule = new TestInstanceModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(testInstanceModalModule).toBeTruthy();

+  });

+});

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
index 0000000..96be1e9
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/test-instance-modal/test-instance-modal.module.ts
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { MatInputModule, MatDialogModule, MatFormFieldModule, MatIconModule, MatButtonModule } from '@angular/material';

+import { CreateTestInstanceFormModule } from '../create-test-instance-form/create-test-instance-form.module';

+import { FormsModule } from '@angular/forms';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { TestInstanceModalComponent } from './test-instance-modal.component';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FormsModule,

+    FilterPipeModule,

+    MatInputModule,

+    MatDialogModule,

+    MatFormFieldModule,

+    MatButtonModule,

+    MatIconModule,

+    CreateTestInstanceFormModule

+    

+  ],

+  declarations: [TestInstanceModalComponent],

+  exports: [TestInstanceModalComponent],

+  entryComponents: [TestInstanceModalComponent]

+})

+export class TestInstanceModalModule { }

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
index 0000000..401d807
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.pug
@@ -0,0 +1,44 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+h2.mb-1(mat-dialog-title) Search Users By Email

+//input.bg-light.form-control(mat-dialog-title, type="text", placeholder="Search...", [(ngModel)]="search.testName")

+mat-form-field(style="width:100%")

+  input(matInput, type='search', placeholder='Search email...', color='blue', [(ngModel)]='search.email')

+  button(mat-button, *ngIf='search.email', matSuffix, mat-icon-button, aria-label='Clear', (click)="search.email=''")

+    mat-icon close

+

+mat-dialog-content

+  .row

+    .col-md-8

+      .list-group

+        .px-4.py-3

+          .mr-1.ml-1(*ngFor="let user of users | filterBy:search")

+            mat-checkbox(*ngIf="search.email.length > 0", [(ngModel)]="user.isSelected", (change)="selectUser(user)") 

+              .ml-1

+                h5 {{ user.firstName }} {{user.lastName}} 

+                p.mb-0 {{ user.email }}

+    .col-md-4

+      h4(*ngIf="selectedUsers.length > 0") Selected Users

+      .list-group

+        .mr-1.ml-1(*ngFor="let user of selectedUsers")

+          mat-checkbox([(ngModel)] = "user.isSelected", (change)="unselectUser(user)") 

+            .ml-1

+                h5 {{ user.firstName }} {{user.lastName}} 

+                p.mb-0 {{ user.email }}

+mat-dialog-actions 

+    button.bg-primary.text-white(mat-button, (click)="addUsers()") Add To Group

+               

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..1a42102
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { UserSelectComponent } from './user-select.component';

+

+describe('UserSelectComponent', () => {

+  let component: UserSelectComponent;

+  let fixture: ComponentFixture<UserSelectComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ UserSelectComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(UserSelectComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..5cbfa0b
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/user-select/user-select.component.ts
@@ -0,0 +1,117 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject } from '@angular/core';

+import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';

+import { UserService } from 'app/shared/services/user.service';

+import { GroupService } from 'app/shared/services/group.service';

+

+@Component({

+  selector: 'app-user-select',

+  templateUrl: './user-select.component.pug',

+  styleUrls: ['./user-select.component.scss']

+})

+export class UserSelectComponent implements OnInit {

+

+  

+  public data; 

+  public users; 

+  public group;

+  public search;

+  public selectedUsers;

+

+  constructor(

+    public dialogRef: MatDialogRef<UserSelectComponent>,

+    private userService: UserService,

+    private groupService: GroupService,

+    @Inject(MAT_DIALOG_DATA) public input_data

+  ) {

+    this.data = {};

+   }

+  

+  onNoClick(): void {

+    this.dialogRef.close();

+  }

+

+  selectUser(user){

+    //this.unselectUser();

+    if(user.isSelected){

+      this.selectedUsers.push(user);

+    }else{

+      //user.isSelected = false;

+      this.unselectUser(user);

+    }

+    

+

+  }

+

+  unselectUser(user){

+    // this.selectedUsers = this.selectedUsers.filter(user => user.isSelected);

+    this.selectedUsers.splice(this.selectedUsers.findIndex(function(userN){ return userN._id.toString() === user._id.toString(); }), 1);

+    

+  }

+

+  addUsers(){

+    let usersToAdd = this.selectedUsers;

+

+    //filters users that are already in the group to avoid duplicates

+    if(this.group.members){

+      usersToAdd = this.selectedUsers.filter(user =>

+        this.group.members.filter(member => member.userId.toString() == user._id.toString()).length <= 0

+      );

+    }

+   

+    //populates the users roles and userId field

+    for(let i = 0; i < usersToAdd.length; i++){

+        usersToAdd[i] = {

+          userId : usersToAdd[i]._id,

+          roles : ["user"]

+        }

+    }

+    //sets up patch object

+ 

+    let groupPatch = {

+      _id : this.input_data.groupId,

+      $push : { members: { $each : usersToAdd } }

+      

+    }

+    this.groupService.patch(groupPatch).subscribe((results) => {

+      this.dialogRef.close(usersToAdd);

+    });

+    

+  }

+

+  ngOnInit() {

+    this.users = [];

+    this.selectedUsers = [];

+    this.userService.find({$limit: -1, $select: ['firstName', 'lastName', 'email']})

+      .subscribe(

+        (result) => {

+            this.users = result;

+        },

+        (error) => {

+            console.log(error);

+      });

+      this.groupService.get(this.input_data.groupId).subscribe((res) => {

+        this.group = res;

+      })

+    

+    this.search = {};

+    this.search.email = ""; 

+    this.input_data.testDefinition = {};

+  }

+

+}

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
index 0000000..dc33dc2
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/user-select/user-select.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { UserSelectModule } from './user-select.module';

+

+describe('UserSelectModule', () => {

+  let userSelectModule: UserSelectModule;

+

+  beforeEach(() => {

+    userSelectModule = new UserSelectModule();

+  });

+

+  it('should create an instance', () => {

+    expect(userSelectModule).toBeTruthy();

+  });

+});

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
index 0000000..8793aed
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/user-select/user-select.module.ts
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { UserSelectComponent } from './user-select.component';

+import { FormsModule } from '@angular/forms';

+import { FilterPipeModule } from 'ngx-filter-pipe';

+import { MatButtonModule, MatInputModule, MatDialogModule, MatIconModule, MatFormFieldModule, MatCheckboxModule } from '@angular/material';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FormsModule,

+    FilterPipeModule,

+    MatButtonModule,

+    MatInputModule,

+    MatDialogModule,

+    MatIconModule,

+    MatFormFieldModule,

+    MatCheckboxModule

+  ],

+  declarations: [UserSelectComponent],

+  exports: [UserSelectComponent],

+  entryComponents: [UserSelectComponent]

+})

+export class UserSelectModule { }

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
index 0000000..10da824
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.pug
@@ -0,0 +1,32 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+.row

+  .col-md-6

+    h5 Scheduled Instance

+    Label {{schedule.testInstanceName}}

+    h5 Schedule Start Date

+    Label {{ schedule.data.testSchedule._testInstanceStartDate }}

+    h5 Schedule End Date

+    Label {{schedule.data.testSchedule._testInstanceEndDate}}

+  .col-md-6

+    h5 Next Run On

+    Label {{ schedule.nextRunAt }}

+    h5 Last Run On

+    Label {{schedule.lastRunAt}}

+    h5 Run Every

+    Label {{schedule.repeatInterval}}

+  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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..0e95912
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { ViewScheduleModalComponent } from './view-schedule-modal.component';

+

+describe('ViewScheduleModalComponent', () => {

+  let component: ViewScheduleModalComponent;

+  let fixture: ComponentFixture<ViewScheduleModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ ViewScheduleModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(ViewScheduleModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..c047c92
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.component.ts
@@ -0,0 +1,45 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject } from '@angular/core';

+import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';

+

+@Component({

+  selector: 'app-view-schedule-modal',

+  templateUrl: './view-schedule-modal.component.pug',

+  styleUrls: ['./view-schedule-modal.component.scss']

+})

+export class ViewScheduleModalComponent implements OnInit  {

+  

+  public data; 

+

+  constructor( 

+    private dialogRef: MatDialogRef<ViewScheduleModalComponent>, 

+    private dialog: MatDialog,

+    @Inject(MAT_DIALOG_DATA) public schedule: any

+  ) {

+    }

+

+  onNoClick(): void {

+    this.dialogRef.close();

+  }

+

+  ngOnInit() {

+    if(!this.schedule.data.testSchedule._testInstanceEndDate){

+      this.schedule.data.testSchedule._testInstanceEndDate = 'none';

+    }

+  }

+}

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
index 0000000..cd52d9d
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { ViewScheduleModalModule } from './view-schedule-modal.module';

+

+describe('ViewScheduleModalModule', () => {

+  let viewScheduleModalModule: ViewScheduleModalModule;

+

+  beforeEach(() => {

+    viewScheduleModalModule = new ViewScheduleModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(viewScheduleModalModule).toBeTruthy();

+  });

+});

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
index 0000000..dbb706e
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-schedule-modal/view-schedule-modal.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { MatDialogModule, MatButtonModule } from '@angular/material';

+import { ViewScheduleModalComponent } from './view-schedule-modal.component';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    MatDialogModule,

+    MatButtonModule

+  ],

+  declarations: [ViewScheduleModalComponent],

+  exports: [ViewScheduleModalComponent],

+  entryComponents: [ViewScheduleModalComponent]

+})

+export class ViewScheduleModalModule { }

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
index 0000000..7c33d5e
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.pug
@@ -0,0 +1,23 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+h2.mat-dialog-title

+  span(*ngIf="td") {{ td.testName }} 

+  | Workflow

+mat-dialog-content

+  #canvas(style="width: 100%; height: 100%")

+mat-dialog-actions.pull-right

+  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
index 0000000..90560e3
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.scss
@@ -0,0 +1,20 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+mat-dialog-content {

+    max-height: none;

+    height: calc(100% - 90px);

+}
\ 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
index 0000000..e11459c
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { ViewWorkflowModalComponent } from './view-workflow-modal.component';

+

+describe('ViewWorkflowModalComponent', () => {

+  let component: ViewWorkflowModalComponent;

+  let fixture: ComponentFixture<ViewWorkflowModalComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ ViewWorkflowModalComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(ViewWorkflowModalComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..7cef8dc
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.component.ts
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, Inject, HostListener } from '@angular/core';

+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

+import { TestHeadModalComponent } from '../test-head-modal/test-head-modal.component';

+import { BpmnFactoryService } from 'app/shared/factories/bpmn-factory.service';

+

+

+@Component({

+  selector: 'app-view-workflow-modal',

+  templateUrl: './view-workflow-modal.component.pug',

+  styleUrls: ['./view-workflow-modal.component.scss']

+})

+export class ViewWorkflowModalComponent implements OnInit {

+

+  public viewer;

+

+  constructor(

+    public dialogRef: MatDialogRef<TestHeadModalComponent>, 

+    private bpmnFactory: BpmnFactoryService,

+    @Inject(MAT_DIALOG_DATA) public input_data

+  ) { }

+

+  async ngOnInit() {

+

+    this.viewer = await this.bpmnFactory.setup({

+      mode: 'viewer',

+      options: {

+        container: '#canvas'

+      },

+      xml: this.input_data.xml,

+      fileId: this.input_data.fileId,

+      testDefinitionId: this.input_data.testDefinitionId,

+      version: this.input_data.version

+    });

+

+  }

+

+}

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
index 0000000..a1da0a8
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { ViewWorkflowModalModule } from './view-workflow-modal.module';

+

+describe('ViewWorkflowModalModule', () => {

+  let viewWorkflowModalModule: ViewWorkflowModalModule;

+

+  beforeEach(() => {

+    viewWorkflowModalModule = new ViewWorkflowModalModule();

+  });

+

+  it('should create an instance', () => {

+    expect(viewWorkflowModalModule).toBeTruthy();

+  });

+});

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
index 0000000..5538574
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/view-workflow-modal/view-workflow-modal.module.ts
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { ViewWorkflowModalComponent } from './view-workflow-modal.component';

+import { MAT_DIALOG_DEFAULT_OPTIONS, MatDialogModule, MatButtonModule } from '@angular/material';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    MatDialogModule,

+    MatButtonModule

+  ],

+  declarations: [ViewWorkflowModalComponent],

+  exports: [ViewWorkflowModalComponent],

+  entryComponents: [ViewWorkflowModalComponent],

+  providers: [{provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: {hasBackdrop: true}}]

+})

+export class ViewWorkflowModalModule { }

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
index 0000000..052fbfb
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.pug
@@ -0,0 +1,36 @@
+//-  Copyright (c) 2019 AT&T Intellectual Property.                             #

+//-                                                                             #

+//-  Licensed under the Apache License, Version 2.0 (the "License");            #

+//-  you may not use this file except in compliance with the License.           #

+//-  You may obtain a copy of the License at                                    #

+//-                                                                             #

+//-      http://www.apache.org/licenses/LICENSE-2.0                             #

+//-                                                                             #

+//-  Unless required by applicable law or agreed to in writing, software        #

+//-  distributed under the License is distributed on an "AS IS" BASIS,          #

+//-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+//-  See the License for the specific language governing permissions and        #

+//-  limitations under the License.                                             #

+//- #############################################################################

+

+

+form(style="width:100%")

+  .row

+    .col-sm-6

+      .row

+        mat-form-field.mr-2

+          mat-select((selectionChange)="onFormChange()", name="ns", placeholder="Async", [(value)]="workReq.async", required)

+            mat-option([value]="false") False

+            mat-option([value]="true") True

+      .row

+        mat-form-field.mr-2(*ngIf="workReq")

+          input(matInput, (onChange)="onFormChange()", type="text", name="asyncTopic", placeholder="Async Topic", [(ngModel)]="workReq.asyncTopic")

+      

+    .col-sm-6

+      .row

+        mat-form-field.mr-2

+          input(matInput, (onChange)="onFormChange()", type="text", name="testInstanceId", placeholder="Test Instance Id", [(ngModel)]="workReq.testInstanceId", required)

+      .row

+        mat-form-field

+          input(matInput, (onChange)="onFormChange()", type="number", name="timeoutTime", placeholder="Timeout Time in Millis", [(ngModel)]="workReq.maxExecutionTimeInMillis", required)

+        
\ 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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.scss
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..d789ffb
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.spec.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing';

+

+import { WorkflowRequestComponent } from './workflow-request.component';

+

+describe('WorkflowRequestComponent', () => {

+  let component: WorkflowRequestComponent;

+  let fixture: ComponentFixture<WorkflowRequestComponent>;

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      declarations: [ WorkflowRequestComponent ]

+    })

+    .compileComponents();

+  }));

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(WorkflowRequestComponent);

+    component = fixture.componentInstance;

+    fixture.detectChanges();

+  });

+

+  it('should create', () => {

+    expect(component).toBeTruthy();

+  });

+});

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
index 0000000..35f5042
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.component.ts
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit, EventEmitter, Input, Output } from '@angular/core';

+import { FormGroup } from '@angular/forms';

+

+@Component({

+  selector: 'app-workflow-request',

+  templateUrl: './workflow-request.component.pug',

+  styleUrls: ['./workflow-request.component.scss']

+})

+

+export class WorkflowRequestComponent implements OnInit {

+  @Input() public formData;

+  @Input() public taskId;

+  @Input() public index;

+

+  @Output() public childEvent = new EventEmitter();

+

+  public workReq;

+  constructor() { }

+

+  ngOnInit() {

+    this.workReq = this.formData;

+  }

+

+  onFormChange(){  

+    let event = {

+      object: this.workReq,

+      taskId: this.taskId,

+      index: this.index

+    };

+    this.childEvent.emit(event);

+  }

+}

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
index 0000000..e6d1f84
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { WorkflowRequestModule } from './workflow-request.module';

+

+describe('WorkflowRequestModule', () => {

+  let workflowRequestModule: WorkflowRequestModule;

+

+  beforeEach(() => {

+    workflowRequestModule = new WorkflowRequestModule();

+  });

+

+  it('should create an instance', () => {

+    expect(workflowRequestModule).toBeTruthy();

+  });

+});

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
index 0000000..ca959d9
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/modules/workflow-request/workflow-request.module.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { WorkflowRequestComponent } from './workflow-request.component';

+import { FormsModule, ReactiveFormsModule } from '@angular/forms';

+import { MatButtonModule, MatInputModule, MatSelectModule, MatOptionModule, MatSnackBarModule, MatIconModule, MatDialogModule } from '@angular/material';

+import { AlertSnackbarModule } from '../alert-snackbar/alert-snackbar.module';

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FormsModule,

+    MatButtonModule,

+    MatInputModule,

+    MatSelectModule,

+    MatOptionModule,

+    MatSnackBarModule,

+    AlertSnackbarModule,

+    MatIconModule,

+    ReactiveFormsModule,

+    MatDialogModule

+  ],

+  declarations: [WorkflowRequestComponent],

+  exports: [WorkflowRequestComponent]

+})

+export class WorkflowRequestModule { }

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
index 0000000..c582d92
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/pipes/shared-pipes.module.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+

+@NgModule({

+    imports: [

+        CommonModule

+    ],

+    declarations: [

+   

+    ],

+    exports: [

+   

+    ]

+})

+export class SharedPipesModule { }

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
index 0000000..2485439
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/account.service.spec.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed } from '@angular/core/testing';

+

+import { AccountService } from './account.service';

+

+describe('AccountService', () => {

+  beforeEach(() => TestBed.configureTestingModule({}));

+

+  it('should be created', () => {

+    const service: AccountService = TestBed.get(AccountService);

+    expect(service).toBeTruthy();

+  });

+});

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
index 0000000..cbbb1d3
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/account.service.ts
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { CookieService } from 'ngx-cookie-service';

+import { HttpClient, HttpHeaders } from '@angular/common/http';

+import { Observable } from 'rxjs';

+import { AppGlobals } from '../../app.global';

+import { map } from 'rxjs/operators';

+

+const httpOptions = {

+    headers: new HttpHeaders({ 'Content-Type': 'application/json' })

+};

+

+@Injectable({

+  providedIn: 'root'

+})

+export class AccountService {

+

+    constructor(private cookie: CookieService, private http: HttpClient) { }

+

+

+    verify(token): Observable<Object>{

+        let body = {

+            action: 'verifySignupLong',

+            value: token

+        };

+

+        return this.http.post(AppGlobals.baseAPIUrl + 'authManagement', body, httpOptions)

+

+    }

+

+

+}

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
index 0000000..91de3d6
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/auth.service.spec.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed } from '@angular/core/testing';

+

+import { AuthService } from './auth.service';

+

+describe('AuthService', () => {

+  beforeEach(() => TestBed.configureTestingModule({}));

+

+  it('should be created', () => {

+    const service: AuthService = TestBed.get(AuthService);

+    expect(service).toBeTruthy();

+  });

+});

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
index 0000000..7b5fe3f
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/auth.service.ts
@@ -0,0 +1,85 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { CookieService } from 'ngx-cookie-service';

+import { HttpClient, HttpHeaders } from '@angular/common/http';

+import { Observable } from 'rxjs';

+import { AppGlobals } from '../../app.global';

+import { map } from 'rxjs/operators';

+import { FeathersService } from './feathers.service';

+

+const httpOptions = {

+  headers: new HttpHeaders({ 'Content-Type': 'application/json' })

+};

+

+@Injectable({

+  providedIn: 'root'

+})

+export class AuthService {

+

+  constructor(private cookie: CookieService, private http: HttpClient, private feathers: FeathersService) { }

+

+  //logs user into the app and store the auth token in cookie

+  login(userLogin): Observable<Object> {

+    let body = userLogin;

+    body.strategy = "local";

+    return new Observable(observer => {

+      this.feathers.authenticate(body)

+        .subscribe(res => {

+          this.storeUser(res);

+          observer.next(res);

+        },

+        err => {

+          observer.error(err);

+        });

+    });

+    // return this.http.post(AppGlobals.baseAPIUrl + 'authentication', body, httpOptions)

+    //   .pipe(map(authResult => {

+    //     if (authResult && authResult['accessToken']) {

+    //       this.storeUser(authResult);

+    //     }

+    //     return authResult;

+    //   }));

+  }

+

+  register(user): Observable<Object> {

+    return this.http.post(AppGlobals.baseAPIUrl + 'users', user, httpOptions);

+  }

+

+  //logs user out of app

+  logout() {

+    this.feathers.logout();

+    window.localStorage.clear();

+    this.cookie.delete('access_token');

+    this.cookie.delete('currentUser');

+  }

+

+  //store a user

+  storeUser(user) {

+

+    if (user.accessToken) {

+      window.localStorage.setItem('access_token', user['accessToken'])

+      window.localStorage.setItem('user_rules', JSON.stringify(user['user']['rules']));

+

+      //The rules are too large to store as a cookie

+      delete user['user']['rules'];

+

+      this.cookie.set('access_token', JSON.stringify(user['accessToken']));

+      this.cookie.set('currentUser', JSON.stringify(user['user']));

+    }

+  }

+}

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
index 0000000..66887b4
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/execute.service.ts
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from "@angular/core";

+import { ModelService } from "./model.service";

+import { HttpClient } from "@angular/common/http";

+import { ParamsService } from "./params.service";

+import { CookieService } from "ngx-cookie-service";

+import { FeathersService } from "./feathers.service";

+import { Observable } from "rxjs";

+

+@Injectable({

+    providedIn: 'root'

+})

+

+export class ExecuteService extends ModelService {

+    constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService) {

+        super('execute', http, Params, cookie, feathers);

+    }

+

+    create(data, params?): Observable<Object> {

+        data.asyncTopic = "";

+        return super.create(data, params);

+    }

+}
\ 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
index 0000000..e5ba3eb
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/feathers.service.ts
@@ -0,0 +1,88 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+    

+import { Injectable, OnInit } from '@angular/core';

+

+import * as feathers from '@feathersjs/client';

+import * as io from 'socket.io-client';

+import * as socketio from '@feathersjs/socketio-client';

+import * as authentication from '@feathersjs/authentication-client';

+import { Observable, from, interval } from 'rxjs';

+import { now } from 'moment';

+import { AppGlobals } from 'app/app.global';

+import { CookieService } from 'ngx-cookie-service';

+import { Router } from '@angular/router';

+

+

+@Injectable({

+  providedIn: 'root'

+})

+export class FeathersService {

+  // There are no proper typings available for feathers, due to its plugin-heavy nature

+  private _feathers: any;

+  public _socket: any;

+  public auth: Observable<Object>;

+  

+  constructor(private route: Router) {

+    this._socket = io('/',{

+      transports: ['websocket']

+    });       // init socket.io

+    this._socket.on('connect_error', function(data){

+      route.navigateByUrl('/login');

+    });

+    this._feathers = feathers();                      // init Feathers             // add hooks plugin

+    this._feathers.configure(socketio(this._socket, {

+        timeout: 100000000

+    })); // add socket.io plugin

+    this._feathers.configure(authentication({

+        storage: window.localStorage,

+        storageKey: 'access_token'

+    }));

+

+    //set observiable for services to check before calling the service

+    this.auth = from(this._feathers.authenticate());

+    

+  }

+

+  // expose services

+  public service(name: string) {

+    return this._feathers.service(name);

+  }

+

+  public socket(){

+    return this._socket;

+  }

+

+  // expose authentication

+  public authenticate(credentials?): Observable<Object> { 

+    return new Observable(observer => {

+      this.auth = from(this._feathers.authenticate(credentials).then(res => {

+        observer.next(res);

+      }, err => {

+        observer.error(err);

+        this.route.navigate(['/login'])

+      }));

+    });

+

+  }

+

+  // expose logout

+  public logout() {

+    return this._feathers.logout();

+  }

+}

+

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
index 0000000..7aa654b
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/feedback.service.spec.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed } from '@angular/core/testing';

+

+import { FeedbackService } from './feedback.service';

+

+describe('FeedbackService', () => {

+  beforeEach(() => TestBed.configureTestingModule({}));

+

+  it('should be created', () => {

+    const service: FeedbackService = TestBed.get(FeedbackService);

+    expect(service).toBeTruthy();

+  });

+});

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
index 0000000..a42c2f9
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/feedback.service.ts
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import {HttpClient} from "@angular/common/http";

+import {CookieService} from "ngx-cookie-service";

+import { Observable } from 'rxjs';

+import { ParamsService } from './params.service';

+import { ModelService } from './model.service';

+import { FeathersService } from './feathers.service';

+

+

+@Injectable({

+  providedIn: 'root'

+})

+export class FeedbackService extends ModelService {

+

+    constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService){

+        super('feedback', http, Params, cookie, feathers);

+    }

+

+    sendFeedback(msg): Observable<Object>{

+        let body = {

+            data: msg,

+        };

+        

+        return this.create(body);

+    }

+}

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
index 0000000..322ba32
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/file-transfer.service.spec.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, inject } from '@angular/core/testing';

+

+import { FileTransferService } from './file-transfer.service';

+

+describe('FileTransferService', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      providers: [FileTransferService]

+    });

+  });

+

+  it('should be created', inject([FileTransferService], (service: FileTransferService) => {

+    expect(service).toBeTruthy();

+  }));

+});

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
index 0000000..e607895
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/file-transfer.service.ts
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { ParamsService } from './params.service';

+import { ModelService } from './model.service';

+import { CookieService } from 'ngx-cookie-service';

+import { FeathersService } from './feathers.service';

+ 

+

+@Injectable({

+  providedIn: 'root'

+})

+export class FileTransferService extends ModelService {

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService){

+    super('file-transfer', http, Params, cookie, feathers);

+  }

+

+}

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
index 0000000..3ff1861
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/file.service.spec.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, inject } from '@angular/core/testing';

+

+import { FileService } from './file.service';

+

+describe('FileService', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      providers: [FileService]

+    });

+  });

+

+  it('should be created', inject([FileService], (service: FileService) => {

+    expect(service).toBeTruthy();

+  }));

+});

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
index 0000000..a46c89d
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/file.service.ts
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { ParamsService } from './params.service';

+import { ModelService } from './model.service';

+import { CookieService } from 'ngx-cookie-service';

+import { FeathersService } from './feathers.service';

+ 

+

+@Injectable({

+  providedIn: 'root'

+})

+export class FileService extends ModelService {

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService){

+    super('files', http, Params, cookie, feathers);

+  }

+

+}

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
index 0000000..022d977
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/group.service.spec.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, inject } from '@angular/core/testing';

+

+import { GroupService } from './group.service';

+

+describe('GroupService', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      providers: [GroupService]

+    });

+  });

+

+  it('should be created', inject([GroupService], (service: GroupService) => {

+    expect(service).toBeTruthy();

+  }));

+});

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
index 0000000..477ae92
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/group.service.ts
@@ -0,0 +1,145 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { ParamsService } from './params.service';

+import { ModelService } from './model.service';

+import { CookieService } from 'ngx-cookie-service';

+import { FeathersService } from './feathers.service';

+import { Observable, Subject } from 'rxjs';

+import * as organizeGroups from '../../../../../server/src/feathers/hooks/permissions/get-permissions';

+import { UserService } from './user.service';

+

+@Injectable({

+  providedIn: 'root'

+})

+export class GroupService extends ModelService {

+

+  protected groupList;

+  protected selectedGroup;

+

+  protected groupListChange: Subject<Array<any>> = new Subject<Array<any>>();

+  protected selectedGroupChange: Subject<Object> = new Subject<Object>();

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private _users: UserService) {

+    super('groups', http, Params, cookie, feathers);

+    this.setUp();

+  }

+  //new edit:

+  public currentUser;

+

+  setUp() {

+    let currentId = window.localStorage.getItem('currentGroupId');

+

+    this.currentUser = JSON.parse(this.cookie.get('currentUser'));

+

+    this.find({ $limit: -1, lookup: 'both' }).subscribe(res => {

+      this.setGroupList(res);

+

+      if (currentId) {

+        this.setGroup(this.groupList.filter(elem => elem._id == currentId)[0]);

+      } else if (this.currentUser.defaultGroup) {

+        this.setGroup(this.groupList.filter(elem => elem._id == this.currentUser.defaultGroup)[0]);

+      }else {

+        //set to first group

+      }

+    },

+      err => {

+        console.log(err);

+      })

+  }

+

+

+

+  getGroup() {

+    return this.selectedGroup;

+  }

+

+  getGroupList() {

+    return this.groupList;

+  }

+

+  setGroup(group: any) {

+    this.selectedGroup = group;

+    window.localStorage.setItem('currentGroupId', group._id);

+    this.selectedGroupChange.next(this.selectedGroup);

+    if (!this.currentUser.defaultGroupEnabled) {

+      let userPatch = {

+        _id: this.currentUser._id,

+        defaultGroup: group._id,

+        defaultGroupEnabled: false

+      };

+

+      this._users.patch(userPatch).subscribe((res) => {

+        

+      });

+    }

+  }

+

+  organizeGroups(groups){

+    return organizeGroups(this.currentUser, groups);

+  }

+

+  setGroupList(groups) {

+    this.groupList = organizeGroups(this.currentUser, groups);

+    this.groupListChange.next(this.groupList);

+  }

+

+  listChange(): Subject<Array<any>> {

+    return this.groupListChange;

+  }

+

+  groupChange(): Subject<Object> {

+    return this.selectedGroupChange;

+  }

+

+  format(arr: Array<any>) {

+

+    //puts all groups in a single level array

+    // arr = organizeGroups(this.currentUser, arr);

+

+    var tree = [],

+      mappedArr = {},

+      arrElem,

+      mappedElem;

+

+    // First map the nodes of the array to an object -> create a hash table.

+    for (var i = 0, len = arr.length; i < len; i++) {

+      arrElem = arr[i];

+      mappedArr[arrElem._id] = arrElem;

+      mappedArr[arrElem._id]['children'] = [];

+    }

+

+

+    for (var _id in mappedArr) {

+      if (mappedArr.hasOwnProperty(_id)) {

+        mappedElem = mappedArr[_id];

+        // If the element is not at the root level, add it to its parent array of children.

+        if (mappedElem.parentGroupId) {

+          mappedArr[mappedElem['parentGroupId']]['children'].push(mappedElem);

+        }

+        // If the element is at the root level, add it to first level elements array.

+        else {

+          tree.push(mappedElem);

+        }

+      }

+    }

+    return tree;

+  }

+

+

+}

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
index 0000000..21a0b30
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/health.service.spec.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed } from '@angular/core/testing';

+

+import { HealthService } from './health.service';

+

+describe('HealthService', () => {

+  beforeEach(() => TestBed.configureTestingModule({}));

+

+  it('should be created', () => {

+    const service: HealthService = TestBed.get(HealthService);

+    expect(service).toBeTruthy();

+  });

+});

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
index 0000000..3ad80e3
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/health.service.ts
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { ModelService } from './model.service';

+import { HttpClient } from '@angular/common/http';

+import { ParamsService } from './params.service';

+import { CookieService } from 'ngx-cookie-service';

+import { FeathersService } from './feathers.service';

+import { Observable } from 'rxjs';

+

+@Injectable({

+  providedIn: 'root'

+})

+export class HealthService extends ModelService {

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService){

+    super('health', http, Params, cookie, feathers);

+  }

+  

+  get(id, params?): Observable<any>{

+    return super.call('get', {data: id, params: params}, '/otf/api/health/v1');

+  }

+}

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
index 0000000..2943c05
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/json2html.service.ts
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from "@angular/core";

+import { toInteger } from "@ng-bootstrap/ng-bootstrap/util/util";

+

+@Injectable({

+    providedIn: 'root'

+})

+

+export class ToHtml {

+    constructor() {}

+

+    convert(json:any = [{name: 'Adam', age: 23}, {name: 'Raj', age: 22}, {name: 'Justin', age: 5}], tabs = 0){

+        var html = '';

+        var tabHtml = '';

+        if(typeof json === 'string'){

+          json = JSON.parse(json);

+        }

+        for(let i = 0; i < tabs; i++){

+          tabHtml += '&nbsp;&nbsp;&nbsp;&nbsp;';

+        }

+        for(let key in json){

+          if(json.hasOwnProperty(key)){

+            if(typeof json[key] === "object"){

+              html += tabHtml + '<b><u>' + key + ':</u></b><br/>';

+              if(json.constructor === Array && toInteger(key) > 0){

+                tabs--;

+              }

+              html += this.convert(json[key], ++tabs);

+            }else{

+              html += tabHtml + '<b><u>' + key + ':</u></b>' + '<br/>';

+              if(typeof json[key] === 'string'){

+                json[key] = json[key].replace(/\\n/g, '<br/>' + tabHtml);

+              }

+              html += tabHtml + json[key] + '<br/>';

+              html += '<br/>';

+            }

+          }

+        }

+        return html;

+      }

+    

+    convertString(str){

+        return str.replace(/\\n/g, '<br/>');

+    }

+}
\ 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
index 0000000..07c5331
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/list.service.spec.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, inject } from '@angular/core/testing';

+

+import { ListService } from './list.service';

+

+describe('TestHeadListService', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      providers: [ListService]

+    });

+  });

+

+  it('should be created', inject([ListService], (service: ListService) => {

+    expect(service).toBeTruthy();

+  }));

+});

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
index 0000000..aced885
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/list.service.ts
@@ -0,0 +1,77 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable, OnInit } from '@angular/core';

+import { BehaviorSubject } from 'rxjs';

+

+@Injectable({

+  providedIn: 'root'

+})

+export class ListService {

+

+  listMap: {[uniqueKey: string]: {listSource: any, currentList: any} } = {};

+  // private listSource = new BehaviorSubject(null);

+  // currentList = this.listSource.asObservable();

+

+  constructor() { }

+

+  createList(key){

+    this.listMap[key] = {

+      listSource: new BehaviorSubject(null),

+      currentList: null

+    }

+    this.listMap[key].currentList = this.listMap[key].listSource.asObservable();

+    this.listMap[key].listSource.next([]);

+  }

+

+  changeMessage(key, message: any) {

+    if(!this.listMap[key])

+      this.createList(key);

+

+    this.listMap[key].listSource.next(message)

+  }

+

+  addElement(key, obj: any){

+    this.listMap[key].currentList.subscribe(function(value){

+      //console.log(value);

+      value.push(obj);

+    });

+  }

+

+  removeElement(key, object_field_name, id: any){

+    let val = 0;

+    this.listMap[key].currentList.subscribe(function(value){

+      value.forEach(function(elem, val) {

+        if(elem[object_field_name] == id){

+          value.splice(val, 1);

+        }

+      });

+    });

+    

+  }

+

+  updateElement(key, object_field_name, id: any, new_object){

+    let val = 0;

+    this.listMap[key].currentList.subscribe(function(value){

+      value.forEach(function(elem, val) {

+        if(elem[object_field_name] == id){

+          value[val] = new_object;

+        }

+      })

+    });

+  }

+

+}

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
index 0000000..745e109
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/model.service.ts
@@ -0,0 +1,211 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { HttpClient, HttpHeaders } from "@angular/common/http";

+import { AppGlobals } from "../../app.global";

+import { ParamsService } from "./params.service";

+import { Observable, observable, from } from "rxjs";

+import { CookieService } from "ngx-cookie-service";

+import { FeathersService } from "./feathers.service";

+import { Injectable } from "@angular/core";

+

+Injectable({

+    providedIn: 'root'

+})

+export class ModelService {

+

+    protected path;

+    protected http: HttpClient;

+    protected Params: ParamsService;

+    protected cookie: CookieService;

+    protected feathers: FeathersService;

+    private authenticated: Boolean = false;

+

+    constructor(endpoint: String, http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService) {

+        this.http = http;

+        this.Params = Params;

+        this.path = AppGlobals.baseAPIUrl + endpoint;

+        this.cookie = cookie;

+        this.feathers = feathers;

+    }

+

+    checkAuth(): Observable<Object>{

+        return this.feathers.auth;

+    }

+

+    call(method, data?, path?){

+        if(!path){

+            path = this.path;

+        }

+        return new Observable(observer => {

+            var init = null;

+            if(data.params && data.params.events){

+                delete data.params.events;

+                this.feathers.service(path)

+                    .on('created', data => {

+                        if(init){

+                            if(init.data){

+                                (init.data as Array<Object>).unshift(data);

+                                observer.next(init);

+                            }else{

+                                (init as Array<Object>).unshift(data);

+                                observer.next(init);

+                            }

+                        }

+                    })

+                    .on('removed', data => {

+                        if(init){

+                            if(init.data){

+                                init.data = (init.data as Array<Object>).filter(item => item['_id'] != data._id);

+                                observer.next(init);

+                            }else{

+                                init  = (init as Array<Object>).filter(item => item['_id'] != data._id);

+                                observer.next(init);

+                            }

+                        }

+                    })

+                    .on('updated', data => {

+                        if(init){

+                            if(init.data){

+                                (init.data as Array<Object>).forEach((elem, val) => {

+                                    if(elem['_id'] == data._id){

+                                        (init.data as Array<Object>).splice(val, 1, data);

+                                        return;

+                                    }

+                                })

+                                observer.next(init);

+                            }else{

+                                (init as Array<Object>).forEach((elem, val) => {

+                                    if(elem['_id'] == data._id){

+                                        (init as Array<Object>).splice(val, 1, data);

+                                        return;

+                                    }

+                                })

+                                observer.next(init);

+                            }

+                        }

+                    });

+                

+            }

+            this.checkAuth().subscribe(res => {

+                if(data.data){

+                    

+                    //UPDATE & PATCH

+                    if(method == 'update' || method == 'patch'){

+                        let id = data.data._id;

+                        delete data.data._id;

+                        this.feathers.service(path)[method](id, data.data, {query: data.params}).then(result =>{

+                            if(!init){

+                                init = result;

+                            }

+                            observer.next(result)

+                        }).catch(err => {

+                            observer.error(err)}

+                            );

+                    }else{

+                        this.feathers.service(path)[method](data.data, {query: data.params}).then(result =>{

+                            if(!init){

+                                init = result;

+                            }

+                            observer.next(result)

+                        }).catch(err => {

+                            observer.error(err)

+                        });

+                    }

+                }else{

+                    this.feathers.service(path)[method]({query: data.params}).then(result =>{

+                        if(!init){

+                            init = result;

+                        }

+                        observer.next(result)

+                    }).catch(err => observer.error(err));

+                }

+

+            }, err => {

+                

+                this.feathers.authenticate().subscribe(res => {

+                    observer.next(this.call(method, data, path));

+                })

+            });

+        })  

+    }

+

+    on(event){

+        return new Observable(observer => {

+            this.feathers.service(this.path).on(event, (data) => {

+                observer.next(data);

+            });

+        })

+    }

+

+    // sfind(params = []): Observable<Object> {

+    //     return this.http.get(this.path + this.Params.toString(params), this.getHttpOptions());

+    // }

+

+    find(params?): Observable<Object> {

+

+        return this.call('find', {params: params})

+    }

+

+    // sget(id, params = []): Observable<Object> {

+    //     return from(this.http.get(this.path + '/' + id + this.Params.toString(params), this.getHttpOptions()));

+    // }

+

+    get(id, params?): Observable<Object> {

+        return this.call('get', {data: id, params: params})

+    }

+

+    // create(data, params = []): Observable<Object> {

+    //     return this.http.post(this.path + this.Params.toString(params), data, this.getHttpOptions());

+    // }

+

+    create(data, params?): Observable<Object> {

+        return this.call('create', {data: data, params: params})

+    }

+

+    // update(data, params = []): Observable<Object> {

+    //     return this.http.put(this.path + '/' + data._id + this.Params.toString(params), data, this.getHttpOptions());

+    // }

+

+    update(data, params?): Observable<Object> {

+        return this.call('update', {data: data, params: params})

+    }

+

+    // patch(data, params = []): Observable<Object> {

+    //     return this.http.patch(this.path + '/' + data._id + this.Params.toString(params), data, this.getHttpOptions());

+    // }

+

+    patch(data, params?): Observable<Object> {

+        return this.call('patch', {data: data, params: params})

+    }

+

+    // delete(id, params = []): Observable<Object> {

+    //     return this.http.delete(this.path + '/' + id + this.Params.toString(params), this.getHttpOptions());

+    // }

+

+    delete(id, params?): Observable<Object> {

+        return this.call('remove', {data: id, params: params})

+    }

+

+    protected getHttpOptions() {

+        return {

+            headers: new HttpHeaders({

+                'Authorization': 'Bearer ' + JSON.parse(this.cookie.get('access_token'))

+            })

+        };

+    }

+

+}
\ 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
index 0000000..cbff2dd
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/params.service.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from "@angular/core";

+

+

+@Injectable({

+  providedIn: 'root'

+})

+export class ParamsService {

+    

+    toString(params = []) {

+        var string = "?";

+        params.forEach(elem => {

+            string += elem + '&&';

+        });

+        return string;

+    }

+}
\ 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
index 0000000..4c0f573
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/scheduling.service.spec.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, inject } from '@angular/core/testing';

+

+import { SchedulingService } from './scheduling.service';

+

+describe('SchedulingService', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      providers: [SchedulingService]

+    });

+  });

+

+  it('should be created', inject([SchedulingService], (service: SchedulingService) => {

+    expect(service).toBeTruthy();

+  }));

+});

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
index 0000000..b492ea6
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/scheduling.service.ts
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { HttpClient, HttpHeaders } from "@angular/common/http";

+import { AppGlobals } from "../../app.global";

+import { ParamsService } from "./params.service";

+import { Observable } from "rxjs";

+import { Injectable } from "@angular/core";

+import { ModelService } from './model.service';

+import { CookieService } from "ngx-cookie-service";

+import { TestInstanceService } from "./test-instance.service";

+import { MatDialog } from "@angular/material";

+import { TestDefinitionService } from "./test-definition.service";

+import { ExecuteService } from "./execute.service";

+import { FeathersService } from "./feathers.service";

+

+@Injectable({

+  providedIn: 'root'

+})

+

+export class SchedulingService extends ModelService {

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private td: TestDefinitionService, private instance: TestInstanceService, private execute: ExecuteService, private dialog: MatDialog) {

+    super('jobs', http, Params, cookie, feathers);  

+  }

+

+  // create(data, params?): Observable<Object> {

+  //   return new Observable((observer) => {

+  //     this.instance.get(data.testInstanceId, { $select: ['testData'] }).subscribe(result => {

+  //       if(result){

+  //         super.create(data).subscribe(

+  //           res => {

+  //             observer.next(res);

+  //           },

+  //           err => {

+  //             observer.error(err);

+  //           }

+  //         )

+  //       }        

+  //     });

+  //   });

+  // }

+

+

+}
\ 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
index 0000000..ef6f5e7
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/test-definition.service.spec.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed } from '@angular/core/testing';

+

+import { TestDefinitionService } from './test-definition.service';

+

+describe('TestDefinitionService', () => {

+  beforeEach(() => TestBed.configureTestingModule({}));

+

+  it('should be created', () => {

+    const service: TestDefinitionService = TestBed.get(TestDefinitionService);

+    expect(service).toBeTruthy();

+  });

+});

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
index 0000000..100696d
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/test-definition.service.ts
@@ -0,0 +1,89 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { AppGlobals } from '../../app.global';

+import { Observable } from 'rxjs';

+import { ParamsService } from './params.service';

+import { CookieService } from 'ngx-cookie-service';

+import { ModelService } from './model.service';

+import { FeathersService } from './feathers.service';

+import { GroupService } from './group.service';

+

+

+

+@Injectable({

+  providedIn: 'root'

+})

+

+export class TestDefinitionService extends ModelService {

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private _groups: GroupService) {

+    super('test-definitions', http, Params, cookie, feathers);

+    this.deployAll();

+  }

+

+  create(data, params?): Observable<Object>{

+    this.setGroup(data);

+    return super.create(data, params);

+  }

+

+  validate(testDefinition): Observable<Object> {

+    return this.call('create', {data: { testDefinition: testDefinition } }, AppGlobals.baseAPIUrl + 'bpmn-validate')

+    //return this.http.post(AppGlobals.baseAPIUrl + 'bpmn-validate', {testDefinition: testDefinition}, this.getHttpOptions());

+  }

+

+  validateSave(testDefinition): Observable<Object> {

+    return this.call('update', { data: {_id: null, testDefinition: testDefinition } }, AppGlobals.baseAPIUrl + 'bpmn-validate')

+    //return this.http.put(AppGlobals.baseAPIUrl + 'bpmn-validate', {testDefinition: testDefinition}, this.getHttpOptions());

+  }

+

+  check(processDefinitionKey): Observable<Object>{

+    return this.call('get', {data: processDefinitionKey} , AppGlobals.baseAPIUrl + 'bpmn-validate')

+    //return this.http.get(AppGlobals.baseAPIUrl + 'bpmn-validate/' + processDefinitionKey, this.getHttpOptions());

+  }

+

+  deploy(testDefinition, versionName?): Observable<Object> {

+    let data = {testDefinition: testDefinition};

+

+    if(versionName != null && versionName != undefined){

+      data['version'] = versionName;

+    }

+    return this.call('create', {data: data }, AppGlobals.baseAPIUrl + 'bpmn-upload')

+    //return this.http.post(AppGlobals.baseAPIUrl + 'bpmn-upload', {testDefinition: testDefinition}, this.getHttpOptions());

+  }

+

+  deployAll(){

+    // this.find({$limit: -1}).subscribe(definitions => {

+    //   //definitions = definitions['data'];

+    //   (definitions as Array<Object>).forEach((elem, val) => {

+    //     elem['bpmnInstances'].forEach((e , v) => {

+    //       let el = e;

+    //       this.deploy(elem, el.version).subscribe(res => {

+    //         console.log(res);

+    //       });

+    //     })

+    //   })

+    // })

+  }

+

+  private setGroup(data){

+    if(!data['groupId']){

+      data['groupId'] = this._groups.getGroup()['_id'];

+    }

+  }

+}

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
index 0000000..5b55bfd
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/test-execution.service.spec.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, inject } from '@angular/core/testing';

+

+import { TestExecutionService } from './test-execution.service';

+

+describe('TestExecutionService', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      providers: [TestExecutionService]

+    });

+  });

+

+  it('should be created', inject([TestExecutionService], (service: TestExecutionService) => {

+    expect(service).toBeTruthy();

+  }));

+});

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
index 0000000..9187c9d
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/test-execution.service.ts
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { AppGlobals } from '../../app.global';

+import { ParamsService } from './params.service';

+import { ModelService } from './model.service';

+import { CookieService } from 'ngx-cookie-service';

+import { FeathersService } from './feathers.service';

+

+@Injectable({

+providedIn: 'root'

+})

+export class TestExecutionService extends ModelService {

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService) {

+    super('test-executions', http, Params, cookie, feathers);

+  }

+

+  status(id, params?){

+    return this.call('get', {data: id, params: params}, AppGlobals.baseAPIUrl + 'test-execution-status')

+    //return this.http.get(AppGlobals.baseAPIUrl + 'test-execution-status/' + id + this.Params.toString(params), this.getHttpOptions());

+  }

+}
\ 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
index 0000000..6ed1666
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/test-head.service.spec.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, inject } from '@angular/core/testing';

+

+import { TestHeadService } from './test-head.service';

+

+describe('TestHeadService', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      providers: [TestHeadService]

+    });

+  });

+

+  it('should be created', inject([TestHeadService], (service: TestHeadService) => {

+    expect(service).toBeTruthy();

+  }));

+});

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
index 0000000..ad07560
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/test-head.service.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { ParamsService } from './params.service';

+import { ModelService } from './model.service';

+import { CookieService } from 'ngx-cookie-service';

+import { FeathersService } from './feathers.service';

+import { Observable } from 'rxjs';

+import { GroupService } from './group.service';

+ 

+

+@Injectable({

+  providedIn: 'root'

+})

+export class TestHeadService extends ModelService {

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private _groups: GroupService){

+    super('test-heads', http, Params, cookie, feathers);

+  }

+

+  create(data, params?): Observable<Object> {

+    data['groupId'] = this._groups.getGroup()['_id'];

+    return super.create(data, params);

+  }

+

+}

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
index 0000000..c1319cc
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/test-instance.service.spec.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, inject } from '@angular/core/testing';

+

+import { TestInstanceService } from './test-instance.service';

+

+describe('TestInstanceService', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      providers: [TestInstanceService]

+    });

+  });

+

+  it('should be created', inject([TestInstanceService], (service: TestInstanceService) => {

+    expect(service).toBeTruthy();

+  }));

+});

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
index 0000000..02b82ba
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/test-instance.service.ts
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { ParamsService } from './params.service';

+import { ModelService } from './model.service';

+import { CookieService } from 'ngx-cookie-service';

+import { FeathersService } from './feathers.service';

+import { Observable } from 'rxjs';

+import { GroupService } from './group.service';

+

+@Injectable({

+providedIn: 'root'

+})

+export class TestInstanceService extends ModelService {

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, feathers: FeathersService, private _groups: GroupService){

+    super('test-instances', http, Params, cookie, feathers);

+  }

+

+  create(data, params?):Observable<Object>{

+    data['groupId'] = this._groups.getGroup()['_id'];

+    return super.create(data, params);

+  }

+}

+

+

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
index 0000000..a7662e7
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/user.service.spec.ts
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { TestBed, inject } from '@angular/core/testing';

+

+import { UserService } from './user.service';

+

+describe('UserService', () => {

+  beforeEach(() => {

+    TestBed.configureTestingModule({

+      providers: [UserService]

+    });

+  });

+

+  it('should be created', inject([UserService], (service: UserService) => {

+    expect(service).toBeTruthy();

+  }));

+});

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
index 0000000..0209ab5
--- /dev/null
+++ b/otf-frontend/client/src/app/shared/services/user.service.ts
@@ -0,0 +1,75 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Injectable } from '@angular/core';

+import { HttpClient } from '@angular/common/http';

+import { map } from 'rxjs/operators';

+import { ModelService } from './model.service';

+import { ParamsService } from './params.service';

+import { CookieService } from 'ngx-cookie-service';

+import { FeathersService } from './feathers.service';

+import { Ability } from '@casl/ability';

+

+

+@Injectable({

+  providedIn: 'root'

+})

+export class UserService extends ModelService {

+

+  public ability: Ability;

+

+  constructor(http: HttpClient, Params: ParamsService, cookie: CookieService, private c: CookieService, feathers: FeathersService){

+    super('users', http, Params, cookie, feathers);

+    this.ability = new Ability(JSON.parse(localStorage.getItem('user_rules')));

+  }

+

+  getId(){

+    return JSON.parse(this.cookie.get('currentUser'))._id;

+  }

+

+  // addFavorite(ref: string, id: string){

+  //   return this.get(this.getId()).pipe(map(

+  //     result => {

+  //       if(!result['favorites']){

+  //         result['favorites'] = {};

+  //       }

+  //       if(!result['favorites'][ref]){

+  //         result['favorites'][ref] = [];

+  //       }

+  //       result['favorites'][ref].push(id);

+  //       result['favorites'][ref] = Array.from(new Set(result['favorites'][ref]));

+  //       this.patch(result).subscribe();

+  //     }

+  //   ));

+  // }

+

+  // removeFavorite(ref: string, id: string){

+  //   return this.get(this.getId()).pipe(map(

+  //     result => {

+  //       result['favorites'][ref].splice( result['favorites'][ref].indexOf(id), 1 );

+  //       this.patch(result).subscribe();

+  //     }

+  //   ));

+  // }

+

+  enableUser(id: string, enabled: boolean){

+      return this.patch({

+          "_id" : id,

+          "enabled": enabled

+      })

+

+  }

+}
\ 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
index 0000000..50882e0
--- /dev/null
+++ b/otf-frontend/client/src/app/signup/signup-routing.module.ts
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { Routes, RouterModule } from '@angular/router';

+import { SignupComponent } from './signup.component';

+

+const routes: Routes = [

+    {

+        path: '', component: SignupComponent

+    }

+];

+

+@NgModule({

+    imports: [RouterModule.forChild(routes)],

+    exports: [RouterModule]

+})

+export class SignupRoutingModule {

+}

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
index 0000000..a21c72e
--- /dev/null
+++ b/otf-frontend/client/src/app/signup/signup.component.html
@@ -0,0 +1,68 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<div class="login-page" [@routerTransition]>

+    <div class="row justify-content-md-center">

+        <div class="col-md-4">

+            <img class="user-avatar" src="assets/images/NetworkLogo.jpg" width="150px" />

+            <h1>Open Test Framework</h1>

+            <form role="form">

+                <div class="form-content">

+                    <div class="row justify-content-md-center">

+                        <div class="col-md-6">

+                            <div class="form-group">

+                                <input type="text" required [(ngModel)]="user.firstName" name="firstName" class="form-control input-underline input-lg" id="" placeholder="First Name">

+                            </div>

+

+                            <div class="form-group">

+                                <input type="text" required [(ngModel)]="user.lastName" name="lastName" class="form-control input-underline input-lg" id="" placeholder="Last Name">

+                            </div>

+

+                            <div class="form-group">

+                                <input type="email" required [(ngModel)]="user.email" name="email" class="form-control input-underline input-lg" id="" #email="ngModel" placeholder="Email">

+                            </div>

+                            <div *ngIf="email.invalid && (email.dirty || email.touched)"

+                                class="alert-danger">

+                                <div *ngIf="email.errors.required">

+                                    Email is required.

+                                </div>

+                            </div>

+                        </div>

+                        <div class="col-md-6">

+

+                            <div class="form-group">

+                                <input type="password" required minlength="8" [(ngModel)]="user.password" name="password" class="form-control input-underline input-lg" id="password1" #password="ngModel" placeholder="Password">

+                            </div>

+                            <div *ngIf="password.invalid && (password.dirty || password.touched)"

+                                class="alert-danger">

+                            <div *ngIf="password.errors.required">

+                                Password is required.

+                            </div>

+                            <div *ngIf="password.errors.minlength">

+                                Password must be at least 8 characters long.

+                            </div>

+                            </div>

+                            <div class="form-group">

+                                <input type="password" required [(ngModel)]="passwordConfirm" name="passwordConfirm" class="form-control input-underline input-lg" id="password2" placeholder="Repeat Password">

+                            </div>

+                        </div>

+                    </div>

+                </div>

+                <a class="btn rounded-btn" (click)='register()'> Register </a>&nbsp;

+            </form>

+        </div>

+    </div>

+</div>

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
index 0000000..faabd34
--- /dev/null
+++ b/otf-frontend/client/src/app/signup/signup.component.scss
@@ -0,0 +1,18 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// shared css for the login and signup page

+@import "../login/login.component.scss";

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
index 0000000..e64435d
--- /dev/null
+++ b/otf-frontend/client/src/app/signup/signup.component.spec.ts
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { async, ComponentFixture, TestBed } from '@angular/core/testing'

+import { RouterTestingModule } from '@angular/router/testing'

+import { BrowserAnimationsModule } from '@angular/platform-browser/animations'

+

+import { SignupComponent } from './signup.component'

+import { SignupModule } from './signup.module'

+

+describe('SignupComponent', () => {

+  let component: SignupComponent

+  let fixture: ComponentFixture<SignupComponent>

+

+  beforeEach(async(() => {

+    TestBed.configureTestingModule({

+      imports: [

+        SignupModule,

+        RouterTestingModule,

+        BrowserAnimationsModule,

+      ],

+    })

+    .compileComponents()

+  }))

+

+  beforeEach(() => {

+    fixture = TestBed.createComponent(SignupComponent)

+    component = fixture.componentInstance

+    fixture.detectChanges()

+  })

+

+  it('should create', () => {

+    expect(component).toBeTruthy()

+  })

+})

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
index 0000000..85893e9
--- /dev/null
+++ b/otf-frontend/client/src/app/signup/signup.component.ts
@@ -0,0 +1,102 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { Component, OnInit } from '@angular/core';

+import { routerTransition } from '../router.animations';

+import { HttpClient } from '@angular/common/http';

+import { AppGlobals } from '../app.global';

+import { UserService } from '../shared/services/user.service';

+import { Router } from '@angular/router';

+import { User } from '../shared/models/user.model';

+import { MatDialog } from '@angular/material';

+import { AlertModalComponent } from '../shared/modules/alert-modal/alert-modal.component';

+import { AuthService } from '../shared/services/auth.service';

+

+

+@Component({

+    selector: 'app-signup',

+    templateUrl: './signup.component.html',

+    styleUrls: ['./signup.component.scss'],

+    animations: [routerTransition()]

+})

+export class SignupComponent implements OnInit {

+    public user = {

+        password: null,

+        firstName: null,

+        lastName: null,

+        email: null

+    };

+    public passwordConfirm;

+

+    constructor(public router: Router,

+        private auth: AuthService,

+        public dialog: MatDialog

+        ) {

+           

+        }

+

+    ngOnInit() {

+        

+    }

+

+    register(){

+        // let body  = {

+        //     firstName: this.user.firstName,

+        //     lastName: this.user.lastName,

+        //     email: this.user.email,

+        //     password: this.user.password

+        // };

+        

+        if(this.user.password != this.passwordConfirm){

+            const dialogRef = this.dialog.open(AlertModalComponent, {

+                data:{

+                    type: "Alert",

+                    message: "Passwords must match!"

+                }

+

+            });

+            

+            return;

+        }

+

+        this.auth.register(this.user) 

+            .subscribe(

+                (res) => {

+                    const r = this.dialog.open(AlertModalComponent, {

+                        data: {

+                            type: "Alert",

+                            message: "Check your email to verify your account."

+                        }

+                    });

+

+                    r.afterClosed().subscribe(res => {

+                        this.router.navigateByUrl('/login');

+                    })

+                      

+                },

+                (err) => {

+                    this.dialog.open(AlertModalComponent, {

+                        data:{

+                            type: "Alert",

+                            message: err

+                        }

+                    });

+                }

+            );

+        

+        

+    }

+}

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
index 0000000..9b532fd
--- /dev/null
+++ b/otf-frontend/client/src/app/signup/signup.module.spec.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { SignupModule } from './signup.module';

+

+describe('SignupModule', () => {

+  let signupModule: SignupModule;

+

+  beforeEach(() => {

+    signupModule = new SignupModule();

+  });

+

+  it('should create an instance', () => {

+    expect(signupModule).toBeTruthy();

+  });

+});

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
index 0000000..a9d2ffa
--- /dev/null
+++ b/otf-frontend/client/src/app/signup/signup.module.ts
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { NgModule } from '@angular/core';

+import { CommonModule } from '@angular/common';

+import { FormsModule } from '@angular/forms';

+import { SignupRoutingModule } from './signup-routing.module';

+import { SignupComponent } from './signup.component';

+import { HttpClientModule } from '@angular/common/http';

+import { AppGlobals } from '../app.global';

+import { MatDialogModule, MatButtonModule } from '@angular/material';

+import { AlertModalModule } from '../shared/modules/alert-modal/alert-modal.module';

+

+

+

+@NgModule({

+  imports: [

+    CommonModule,

+    FormsModule,

+    MatDialogModule,

+    MatButtonModule,

+    SignupRoutingModule,

+    AlertModalModule

+  ],

+  declarations: [SignupComponent]

+})

+export class SignupModule { }

diff --git a/otf-frontend/client/src/assets/fakedata.json b/otf-frontend/client/src/assets/fakedata.json
new file mode 100644
index 0000000..80494b9
--- /dev/null
+++ b/otf-frontend/client/src/assets/fakedata.json
@@ -0,0 +1,115 @@
+{

+    "test_heads": [

+        {

+            "test_head_id": 1,

+            "test_head_name": "test1",

+            "description": "Ping Test 1"

+        },

+        {

+            "test_head_id": 2,

+            "test_head_name": "test2",

+            "description": "Ping Test 2"

+        },

+        {

+            "test_head_id": 3,

+            "test_head_name": "test3",

+            "description": "Ping Test 3"

+        },

+        {

+            "test_head_id": 4,

+            "test_head_name": "test4",

+            "description": "Ping Test 4"

+        },

+        {

+            "test_head_id": 5,

+            "test_head_name": "test5",

+            "description": "Ping Test 5"

+        }

+    ],

+    "test_strategies": [

+        {

+            "test_strategy_id": 1,

+            "test_strategy_name": "strategy1",

+            "description": "Recursive Test 1",

+            "vth_nodes": [

+                "node1",

+                "node2"

+            ]

+        },

+        {

+            "test_strategy_id": 2,

+            "test_strategy_name": "strategy2",

+            "description": "Recursive Test 2",

+            "vth_nodes": [

+                "node1"

+            ]

+        },

+        {

+            "test_strategy_id": 3,

+            "test_strategy_name": "strategy3",

+            "description": "Recursive Test 3",

+            "vth_nodes": [

+                "node1",

+                "node2",

+                "node3", 

+                "node4"

+            ]

+        }

+    ],

+    "tests": [

+        {

+            "test_id": 1,

+            "test_name": "Test 1",

+            "creator": 1,

+            "vts": 3,

+            "vth_list": {

+                "node1": 1,

+                "node2": 2,

+                "node3": 3,

+                "node4": 5

+            },

+            "vts_list": [

+

+            ]

+        },

+        {

+            "test_id": 2,

+            "test_name": "Test 2",

+            "creator": 2,

+            "vts": 2,

+            "vth_list": {

+                "node1": 3

+            },

+            "vts_list": [

+

+            ]

+        },

+        {

+            "test_id": 3,

+            "test_name": "Test 3",

+            "creator": 1,

+            "vts": 1,

+            "vth_list": {

+                "node1": 1,

+                "node2": 4

+            },

+            "vts_list": [

+

+            ]

+        }

+    ],

+    "users": [

+        {

+            "user_id": 1,

+            "firstName": "Adam",

+            "lastName": "Ordway",

+            "email": "agordway@gmail.com"

+        },

+        {

+            "user_id": 2,

+            "firstName": "Justin",

+            "lastName": "Meilinger",

+            "email": "mylinger@gmail.com"

+        }

+    ]

+}
\ 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
index 0000000..1936596
--- /dev/null
+++ b/otf-frontend/client/src/assets/i18n/de.json
@@ -0,0 +1,32 @@
+{

+    "Dashboard": "Dashboard",

+    "Charts": "Graphen",

+    "Tables": "Tabellen",

+    "Forms": "Formulare",

+    "Bootstrap Element": "Bootstrap Element",

+    "Bootstrap Grid": "Bootstrap Grid",

+    "Component": "Komponente",

+    "Menu": "Menü",

+    "Submenu": "Submenü",

+    "Blank Page": "Leere Seite",

+    "More Theme": "Mehr Themes",

+    "Download Now": "Jetzt runterladen",

+    "Language": "Sprache",

+    "English": "Englisch",

+    "French": "Französisch",

+    "Urdu": "Urdu",

+    "Spanish": "Spanisch",

+    "Italian": "Italienisch",

+    "Farsi": "Farsi",

+    "German": "Deutsch",

+    "Simplified Chinese": "Vereinfachtes Chinesisch",

+    "Search" : "Suchen",

+    "Settings" : "Einstellungen",

+    "Profile" : "Profil",

+    "Inbox" : "Posteingang",

+    "Log Out" : "Ausloggen",

+    "Pending Task" : "Ausstehende Aufgabe",

+    "In queue" : "In der Warteschlange",

+    "Mail" : "Post",

+    "View All" : "Alle Anzeigen"

+}

diff --git a/otf-frontend/client/src/assets/i18n/en.json b/otf-frontend/client/src/assets/i18n/en.json
new file mode 100644
index 0000000..2b3776f
--- /dev/null
+++ b/otf-frontend/client/src/assets/i18n/en.json
@@ -0,0 +1,32 @@
+{

+    "Dashboard": "Dashboard",

+    "Charts": "Charts",

+    "Tables": "Tables",

+    "Forms": "Forms",

+    "Bootstrap Element": "Bootstrap Element",

+    "Bootstrap Grid": "Bootstrap Grid",

+    "Component": "Component",

+    "Menu": "Menu",

+    "Submenu": "Submenu",

+    "Blank Page": "Blank Page",

+    "More Theme": "More Themes",

+    "Download Now": "Download Now",

+    "Language": "Language",

+    "English": "English",

+    "French": "French",

+    "Urdu": "Urdu",

+    "Spanish": "Spanish",

+    "Italian": "Italian",

+    "Farsi": "Farsi",

+    "German": "German",

+    "Simplified Chinese": "Simplified Chinese",

+    "Search" : "Search",

+    "Settings" : "Settings",

+    "Profile" : "Profile",

+    "Inbox" : "Inbox",

+    "Log Out" : "Log Out",

+    "Pending Task" : "Pending Task",

+    "In queue" : "In queue",

+    "Mail" : "Mail",

+    "View All" : "View All"

+}

diff --git a/otf-frontend/client/src/assets/i18n/es.json b/otf-frontend/client/src/assets/i18n/es.json
new file mode 100644
index 0000000..a225b5f
--- /dev/null
+++ b/otf-frontend/client/src/assets/i18n/es.json
@@ -0,0 +1,32 @@
+{

+    "Dashboard": "Principal",

+    "Charts": "Caracteres",

+    "Tables": "Tablas",

+    "Forms": "Formularios",

+    "Bootstrap Element": "Elementos Bootstrap",

+    "Bootstrap Grid": "Rejilla Bootstrap",

+    "Component": "Componentes",

+    "Menu": "Menú",

+    "Submenu": "Submenú",

+    "Blank Page": "Página en Blanco",

+    "More Theme": "Más temas",

+    "Download Now": "Descarga Ahora",

+    "Language": "Idioma",

+    "English": "Inglés",

+    "French": "Francés",

+    "Urdu": "Urdu",

+    "Spanish": "Español",

+    "Italian": "Italiano",

+    "Farsi": "Farsi",

+    "German": "Alemán",

+    "Simplified Chinese": "Chino simplificado",

+    "Search" : "Búsqueda",

+    "Settings" : "Ajustes",

+    "Profile" : "Profile",

+    "Inbox" : "Bandeja de entrada",

+    "Log Out" : "Cerrar Sesión",

+    "Pending Task" : "Tarea pendiente",

+    "In queue" : "En cola",

+    "Mail" : "Correo",

+    "View All" : "Ver todo"

+}

diff --git a/otf-frontend/client/src/assets/i18n/fa.json b/otf-frontend/client/src/assets/i18n/fa.json
new file mode 100644
index 0000000..508095a
--- /dev/null
+++ b/otf-frontend/client/src/assets/i18n/fa.json
@@ -0,0 +1,32 @@
+{

+    "Dashboard": "داشبورد",

+    "Charts": "چارت ها",

+    "Tables": "جداول",

+    "Forms": "فرم ها",

+    "Bootstrap Element": "عناصر بوتسترپ",

+    "Bootstrap Grid": "جداول بوتسترپ",

+    "Component": "کامپوننت",

+    "Menu": "منوها",

+    "Submenu": "زیر منوها",

+    "Blank Page": "صفحه خالی",

+    "More Theme": "تم های بیشتر",

+    "Download Now": "دانلود",

+    "Language": "زبان",

+    "English": "انگلیسی",

+    "French": "فرانسوی",

+    "Urdu": "اردو",

+    "Spanish": "اسپانیایی",

+    "Italian": "ایتالیایی",

+    "Farsi": "فارسی",

+    "German": "آلمانی",

+    "Simplified Chinese": "چینی ساده شده",

+    "Search" : "جستجو",

+    "Settings" : "تنظیمات",

+    "Profile" : "مشخصات",

+    "Inbox" : "صندوق ورودی",

+    "Log Out" : "خروج از سیستم",

+    "Pending Task" : "وظایف در انتظار",

+    "In queue" : "در صف",

+    "Mail" : "ایمیل",

+    "View All" : "نمایش همه"

+}

diff --git a/otf-frontend/client/src/assets/i18n/fr.json b/otf-frontend/client/src/assets/i18n/fr.json
new file mode 100644
index 0000000..7e27a4f
--- /dev/null
+++ b/otf-frontend/client/src/assets/i18n/fr.json
@@ -0,0 +1,32 @@
+{

+    "Dashboard": "Tableau de bord",

+    "Charts": "Hit-parade",

+    "Tables": "Tableaux",

+    "Forms": "Froms",

+    "Bootstrap Element": "Bootstrap Élément",

+    "Bootstrap Grid": "Bootstrap Grille",

+    "Component": "Composant",

+    "Menu": "Menu",

+    "Submenu": "Sous-menu",

+    "Blank Page": "Blanc Page",

+    "More Theme": "Plus Thèmes",

+    "Download Now": "Télécharger",

+    "Language": "Langue",

+    "English": "Anglais",

+    "French": "Français",

+    "Urdu": "Ourdou",

+    "Spanish": "Spanish",

+    "Italian": "Italien",

+    "Farsi": "Farsi",

+    "German": "Allemand",

+    "Simplified Chinese": "Chinois Simplifié",

+    "Search" : "Chercher",

+    "Settings" : "Paramètres",

+    "Profile" : "Profile",

+    "Inbox" : "Boîte de réception",

+    "Log Out" : "Connectez - Out",

+    "Pending Task" : "Tâche en attente",

+    "In queue" : "Dans la queue",

+    "Mail" : "Courrier",

+    "View All" : "Voir tout"

+}

diff --git a/otf-frontend/client/src/assets/i18n/it.json b/otf-frontend/client/src/assets/i18n/it.json
new file mode 100644
index 0000000..8ee3ae3
--- /dev/null
+++ b/otf-frontend/client/src/assets/i18n/it.json
@@ -0,0 +1,32 @@
+{

+    "Dashboard": "Principale",

+    "Charts": "Grafici",

+    "Tables": "Tabelle",

+    "Forms": "Formulari",

+    "Bootstrap Element": "Elementi Bootstrap",

+    "Bootstrap Grid": "Griglia Bootstrap",

+    "Component": "Componenti",

+    "Menu": "Menu",

+    "Submenu": "Submenu",

+    "Blank Page": "Pagina in Bianco",

+    "More Theme": "Altri temi",

+    "Download Now": "Scarica Adesso",

+    "Language": "Lingua",

+    "English": "Inglese",

+    "French": "Francese",

+    "Urdu": "Urdu",

+    "Spanish": "Spagnolo",

+    "Italian": "Italiano",

+    "Farsi": "Farsi",

+    "German": "Tedesco",

+    "Simplified Chinese": "Cinese semplificato",

+    "Search" : "Ricerca",

+    "Settings" : "Impostazioni",

+    "Profile" : "Profilo",

+    "Inbox" : "Posta in arrivo",

+    "Log Out" : "Uscire",

+    "Pending Task" : "Attività in sospeso",

+    "In queue" : "In coda",

+    "Mail" : "Posta",

+    "View All" : "Visualizza tutti"

+}

diff --git a/otf-frontend/client/src/assets/i18n/ur.json b/otf-frontend/client/src/assets/i18n/ur.json
new file mode 100644
index 0000000..0fd04f3
--- /dev/null
+++ b/otf-frontend/client/src/assets/i18n/ur.json
@@ -0,0 +1,32 @@
+{

+    "Dashboard": "داشبورد",

+    "Charts": "چارت ها",

+    "Tables": "جداول",

+    "Forms": "فرم ها",

+    "Bootstrap Element": "عنصر بوتسترپ",

+    "Bootstrap Grid": "جدول بوتسترپ",

+    "Component": "کامپوننت",

+    "Menu": "منو",

+    "Submenu": "زیر منو",

+    "Blank Page": "صفحه خالی",

+    "More Theme": "تم های بیشتر",

+    "Download Now": "دانلود",

+    "Language": "زبان",

+    "English": "انگریزی",

+    "French": "فرانسیسی",

+    "Urdu": "اردو",

+    "Spanish": "ہسپانوی",

+    "Italian": "اطالوی",

+    "Farsi": "فارسی",

+    "German": "جرمن",

+    "Simplified Chinese": "چینی چینی",

+    "Search" : "تلاش کریں",

+    "Settings" : "ترتیبات",

+    "Profile" : "پروفائل",

+    "Inbox" : "ان باکس",

+    "Log Out" : "لاگ آوٹ",

+    "Pending Task" : "زیر التواء ٹاسک",

+    "In queue" : "قطار میں",

+    "Mail" : "میل",

+    "View All" : "سب دیکھیں"

+}

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
index 0000000..ae60c29
--- /dev/null
+++ b/otf-frontend/client/src/assets/i18n/zh-CHS.json
@@ -0,0 +1,32 @@
+{

+    "Dashboard": "仪表板",

+    "Charts": "图表",

+    "Tables": "表格",

+    "Forms": "表单",

+    "Bootstrap Element": "Bootstrap 元素",

+    "Bootstrap Grid": "Bootstrap 网格",

+    "Component": "组件",

+    "Menu": "菜单",

+    "Submenu": "子菜单",

+    "Blank Page": "空白页",

+    "More Theme": "更多主题",

+    "Download Now": "现在下载",

+    "Language": "语言",

+    "English": "英语",

+    "French": "法语",

+    "Urdu": "乌尔都语",

+    "Spanish": "西班牙语",

+    "Italian": "意大利语",

+    "Farsi": "波斯语",

+    "German": "德语",

+    "Simplified Chinese": "简体中文",

+    "Search" : "搜索",

+    "Settings" : "设置",

+    "Profile" : "个人配置",

+    "Inbox" : "收件箱",

+    "Log Out" : "退出",

+    "Pending Task" : "挂起任务",

+    "In queue" : "队列中",

+    "Mail" : "邮件",

+    "View All" : "查看所有"

+}

diff --git a/otf-frontend/client/src/assets/images/404image.png b/otf-frontend/client/src/assets/images/404image.png
new file mode 100644
index 0000000..5101cc1
--- /dev/null
+++ b/otf-frontend/client/src/assets/images/404image.png
Binary files 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
index 0000000..3c071d3
--- /dev/null
+++ b/otf-frontend/client/src/assets/images/NetworkLogo.jpg
Binary files 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
index 0000000..1e61906
--- /dev/null
+++ b/otf-frontend/client/src/assets/images/OtfIcon.png
Binary files 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
index 0000000..22ccbe3
--- /dev/null
+++ b/otf-frontend/client/src/assets/images/equalizer.gif
Binary files 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
index 0000000..a931536
--- /dev/null
+++ b/otf-frontend/client/src/assets/images/logo.png
Binary files 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
index 0000000..5b1b943
--- /dev/null
+++ b/otf-frontend/client/src/assets/images/networkBackground.jpg
Binary files 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
index 0000000..fc4d343
--- /dev/null
+++ b/otf-frontend/client/src/assets/images/networkBackground1.jpg
Binary files 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
index 0000000..9859565
--- /dev/null
+++ b/otf-frontend/client/src/assets/images/slider1.jpg
Binary files 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
index 0000000..794d791
--- /dev/null
+++ b/otf-frontend/client/src/assets/images/slider2.jpg
Binary files 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
index 0000000..d310b7e
--- /dev/null
+++ b/otf-frontend/client/src/assets/workflows/blank.bpmn
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<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">

+  <bpmn:process id="" isExecutable="true">

+    <bpmn:startEvent id="StartEvent_1" />

+  </bpmn:process>

+  <bpmndi:BPMNDiagram id="BPMNDiagram_1">

+    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="">

+      <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">

+        <dc:Bounds x="173" y="102" width="36" height="36" />

+      </bpmndi:BPMNShape>

+    </bpmndi:BPMNPlane>

+  </bpmndi:BPMNDiagram>

+</bpmn:definitions>

diff --git a/otf-frontend/client/src/environments/environment.prod.ts b/otf-frontend/client/src/environments/environment.prod.ts
new file mode 100644
index 0000000..fe1834b
--- /dev/null
+++ b/otf-frontend/client/src/environments/environment.prod.ts
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+export const environment = {

+  production: true

+};

diff --git a/otf-frontend/client/src/environments/environment.ts b/otf-frontend/client/src/environments/environment.ts
new file mode 100644
index 0000000..98eb372
--- /dev/null
+++ b/otf-frontend/client/src/environments/environment.ts
@@ -0,0 +1,24 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// The file contents for the current environment will overwrite these during build.

+// The build system defaults to the dev environment which uses `environment.ts`, but if you do

+// `ng build --env=prod` then `environment.prod.ts` will be used instead.

+// The list of which env maps to which file can be found in `.angular-cli.json`.

+

+export const environment = {

+  production: false

+};

diff --git a/otf-frontend/client/src/favicon.ico b/otf-frontend/client/src/favicon.ico
new file mode 100644
index 0000000..2cfcef1
--- /dev/null
+++ b/otf-frontend/client/src/favicon.ico
Binary files differ
diff --git a/otf-frontend/client/src/global-shims.ts b/otf-frontend/client/src/global-shims.ts
new file mode 100644
index 0000000..c6bec7f
--- /dev/null
+++ b/otf-frontend/client/src/global-shims.ts
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+(window as any).global = window;

+

+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
index 0000000..f6daf32
--- /dev/null
+++ b/otf-frontend/client/src/index.html
@@ -0,0 +1,36 @@
+<!-- Copyright (c) 2019 AT&T Intellectual Property.                            #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+#############################################################################-->

+

+

+<!doctype html>

+    <html lang="en">

+    <head>

+        <meta charset="utf-8">

+        <title>Open Test Framework</title>

+        <base href="/">

+

+        <meta name="viewport" content="width=device-width, initial-scale=1">

+        <link rel="icon" type="image/x-icon" href="favicon.ico">

+    </head>

+    <body>

+        <app-root>

+            <div class="spinner">

+                <div class="bounce1"></div>

+                <div class="bounce2"></div>

+                <div class="bounce3"></div>

+            </div>

+        </app-root>

+    </body>

+</html>

diff --git a/otf-frontend/client/src/main.ts b/otf-frontend/client/src/main.ts
new file mode 100644
index 0000000..fe30c76
--- /dev/null
+++ b/otf-frontend/client/src/main.ts
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+import { enableProdMode } from '@angular/core';

+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

+

+import { AppModule } from './app/app.module';

+import { environment } from './environments/environment';

+

+if (environment.production) {

+    enableProdMode();

+}

+

+platformBrowserDynamic()

+    .bootstrapModule(AppModule)

+    .catch(err => console.log(err));

diff --git a/otf-frontend/client/src/polyfills.ts b/otf-frontend/client/src/polyfills.ts
new file mode 100644
index 0000000..404f96f
--- /dev/null
+++ b/otf-frontend/client/src/polyfills.ts
@@ -0,0 +1,85 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+/**

+ * This file includes polyfills needed by Angular and is loaded before the app.

+ * You can add your own extra polyfills to this file.

+ *

+ * This file is divided into 2 sections:

+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.

+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main

+ *      file.

+ *

+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that

+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),

+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.

+ *

+ * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html

+ */

+

+/***************************************************************************************************

+ * BROWSER POLYFILLS

+ */

+

+/** IE9, IE10 and IE11 requires all of the following polyfills. **/

+import 'core-js/es6/symbol';

+import 'core-js/es6/object';

+import 'core-js/es6/function';

+import 'core-js/es6/parse-int';

+import 'core-js/es6/parse-float';

+import 'core-js/es6/number';

+import 'core-js/es6/math';

+import 'core-js/es6/string';

+import 'core-js/es6/date';

+import 'core-js/es6/array';

+import 'core-js/es6/regexp';

+import 'core-js/es6/map';

+import 'core-js/es6/weak-map';

+import 'core-js/es6/set';

+

+(window as any).global = window;

+

+/** IE10 and IE11 requires the following for NgClass support on SVG elements */

+//import 'classlist.js';  // Run `npm install --save classlist.js`.

+

+/** Evergreen browsers require these. **/

+import 'core-js/es6/reflect';

+import 'core-js/es7/reflect';

+

+/**

+ * Required to support Web Animations `@angular/platform-browser/animations`.

+ * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation

+ **/

+//import 'web-animations-js';  // Run `npm install --save web-animations-js`.

+

+/***************************************************************************************************

+ * Zone JS is required by Angular itself.

+ */

+import 'zone.js/dist/zone'; // Included with Angular CLI.

+

+/***************************************************************************************************

+ * APPLICATION IMPORTS

+ */

+

+/**

+ * Date, currency, decimal and percent pipes.

+ * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10

+ */

+// import 'intl';  // Run `npm install --save intl`.

+/**

+ * Need to import at least one locale-data with intl.

+ */

+// import 'intl/locale-data/jsonp/en';

diff --git a/otf-frontend/client/src/styles/_responsive.scss b/otf-frontend/client/src/styles/_responsive.scss
new file mode 100644
index 0000000..a5509a0
--- /dev/null
+++ b/otf-frontend/client/src/styles/_responsive.scss
@@ -0,0 +1,23 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+@media screen and (max-width: 992px) {

+    .push-right {

+        .sidebar {

+            left: 235px !important;

+        }

+    }

+}

diff --git a/otf-frontend/client/src/styles/_rtl.scss b/otf-frontend/client/src/styles/_rtl.scss
new file mode 100644
index 0000000..1249952
--- /dev/null
+++ b/otf-frontend/client/src/styles/_rtl.scss
@@ -0,0 +1,90 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.rtl {

+    .sidebar {

+        left: auto !important;

+        right: 0 !important;

+        > ul.list-group {

+            padding: 0;

+        }

+    }

+    .main-container {

+        margin-left: 0 !important;

+        margin-right: 235px;

+    }

+    /*rtl dropdown items correction*/

+    .dropdown-menu {

+        text-align: right;

+    }

+    * {

+        direction: rtl;

+    }

+    .navbar * {

+        direction: ltr;

+    }

+

+    .sidebar * {

+        direction: ltr;

+    }

+

+    .navbar .dropdown-menu {

+        text-align: left;

+    }

+

+    .breadcrumb {

+        direction: ltr;

+        justify-content: flex-end;

+

+        * {

+            direction: ltr;

+        }

+    }

+

+    .datepicker-input {

+        direction: ltr;

+        .dropdown-menu {

+            direction: ltr;

+

+            * {

+                direction: ltr;

+            }

+        }

+    }

+

+    .input-group {

+        direction: ltr;

+    }

+}

+@media screen and (max-width: 992px) {

+    .rtl {

+        .navbar-brand {

+            direction: ltr;

+        }

+        .sidebar {

+            right: -235px !important;

+        }

+        .main-container {

+            margin-right: 0;

+        }

+        &.push-right {

+            .sidebar {

+                left: auto !important;

+                right: 0 !important;

+            }

+        }

+    }

+}

diff --git a/otf-frontend/client/src/styles/_spinner.scss b/otf-frontend/client/src/styles/_spinner.scss
new file mode 100644
index 0000000..8d24c4d
--- /dev/null
+++ b/otf-frontend/client/src/styles/_spinner.scss
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.spinner {

+    position: absolute;

+    top: 50%;

+    left: 50%;

+    -ms-transform: translate(-50%,-50%); /* IE 9 */

+    -webkit-transform: translate(-50%,-50%); /* Safari */

+    transform: translate(-50%,-50%); /* Standard syntax */

+    width: 70px;

+    height: 70px;

+    > div {

+        width: 18px;

+        height: 18px;

+        background-color: #333;

+

+        border-radius: 100%;

+        display: inline-block;

+        -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;

+        animation: sk-bouncedelay 1.4s infinite ease-in-out both;

+    }

+    .bounce1 {

+        -webkit-animation-delay: -0.32s;

+        animation-delay: -0.32s;

+    }

+

+    .bounce2 {

+        -webkit-animation-delay: -0.16s;

+        animation-delay: -0.16s;

+    }

+}

+

+@-webkit-keyframes sk-bouncedelay {

+    0%, 80%, 100% { -webkit-transform: scale(0) }

+    40% { -webkit-transform: scale(1.0) }

+}

+

+@keyframes sk-bouncedelay {

+    0%, 80%, 100% {

+        -webkit-transform: scale(0);

+        transform: scale(0);

+    } 40% {

+        -webkit-transform: scale(1.0);

+        transform: scale(1.0);

+    }

+}

diff --git a/otf-frontend/client/src/styles/_utils.scss b/otf-frontend/client/src/styles/_utils.scss
new file mode 100644
index 0000000..61014d6
--- /dev/null
+++ b/otf-frontend/client/src/styles/_utils.scss
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+.fs-12 {

+    font-size: 12px;

+}

diff --git a/otf-frontend/client/src/styles/app.scss b/otf-frontend/client/src/styles/app.scss
new file mode 100644
index 0000000..3e6516b
--- /dev/null
+++ b/otf-frontend/client/src/styles/app.scss
@@ -0,0 +1,187 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+/* You can add global styles to this file, and also import other style files */

+$zindex-dropdown:                   900 !default;

+$zindex-sticky:                     920 !default;

+$zindex-fixed:                      950 !default;

+$font-size-base:                    .8rem !default;

+@import "../../../node_modules/bootstrap/scss/bootstrap";

+@import "spinner";

+@import "utils";

+@import "rtl";

+@import "responsive";

+

+.list-group-item {

+    padding: .5rem 1rem;

+}

+

+@media print {

+    .breadcrumb {

+        display: none !important;

+    }

+}

+

+@import '~@angular/material/theming';

+// Plus imports for other components in your app.

+

+// Include the common styles for Angular Material. We include this here so that you only

+// have to load a single css file for Angular Material in your app.

+// Be sure that you only ever include this mixin once!

+@include mat-core();

+

+// Define the palettes for your theme using the Material Design palettes available in palette.scss

+// (imported above). For each palette, you can optionally specify a default, lighter, and darker

+// hue. Available color palettes: https://material.io/design/color/

+$candy-app-primary: mat-palette($mat-blue, 900);

+

+$candy-app-accent:  mat-palette($mat-orange, A400);

+

+// The warn palette is optional (defaults to red).

+$candy-app-warn:    mat-palette($mat-deep-orange, A700);

+

+// Create the theme object (a Sass map containing all of the palettes).

+$candy-app-theme: mat-light-theme($candy-app-primary, $candy-app-accent, $candy-app-warn);

+

+// Include theme styles for core and each component used in your app.

+// Alternatively, you can import and @include the theme mixins for each component

+// that you are using.

+@include angular-material-theme($candy-app-theme);

+

+@import "../../../node_modules/perfect-scrollbar/css/perfect-scrollbar.css";

+//@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';

+@import url('https://fonts.googleapis.com/icon?family=Material+Icons');

+@import "../../../node_modules/codemirror/lib/codemirror.css";

+@import "../../../node_modules/codemirror/theme/eclipse.css";

+

+@import 'ag-grid-community/dist/styles/ag-grid.css';

+@import 'ag-grid-community/dist/styles/ag-theme-material.css';

+

+@font-face {

+    font-family: 'Material Icons';

+    font-style: normal;

+    font-weight: 400;

+    src: local('Material Icons'), 

+         local('MaterialIcons-Regular'), 

+         url(https://fonts.gstatic.com/s/materialicons/v21/2fcrYFNaTjcS6g4U3t-Y5UEw0lE80llgEseQY3FEmqw.woff2) format('woff2');

+  }

+

+mat-icon{

+    font-family: 'Material Icons' !important

+}

+

+.embedded {

+    box-shadow: inset 0px 11px 8px -10px #CCC, inset 0px -11px 8px -10px #CCC

+}

+

+.mat-mini-fab .mat-button-wrapper>*{

+    vertical-align: 6px !important;

+}

+

+button:focus{

+    outline: none;

+}

+

+mat-card {

+    padding: 0px !important;

+}

+

+mat-card-header {

+    padding: 15px !important;

+    padding-top: 10px !important;

+    padding-bottom: 10px !important;

+}

+

+.mat-card-header-text {

+    width: 100% !important;

+}

+

+mat-card-title {

+    margin: 0px !important;

+    padding: 0px !important;

+}

+

+mat-card-title button {

+    padding: 3px 8px !important;

+    line-height: 5px !important;

+}

+

+mat-card-title button mat-icon {

+    font-size: 22px !important;

+}

+

+mat-card-content {

+    padding: 24px !important;

+    padding-top: 10px !important;

+}

+

+.highlight-task-running:not(.djs-connection) .djs-visual > :nth-child(1) {

+    fill: rgb(186, 186, 255) !important; /* color elements as green */

+    opacity: .7;

+}

+

+.highlight-task-completed:not(.djs-connection) .djs-visual > :nth-child(1) {

+    fill: rgb(92, 223, 92) !important; /* color elements as green */

+}

+

+.highlight-task-failed:not(.djs-connection) .djs-visual > :nth-child(1) {

+    fill: rgb(255, 83, 83) !important; /* color elements as green */

+}

+

+.dropdown-item {

+    cursor: pointer;

+}

+

+tr:nth-child(even){background-color: #f9f9f9;}

+

+html {

+    height: 100%

+}

+

+body {

+    height: calc(100% - 56px);

+}

+

+.main-container {

+    height: 100%;

+    overflow: scroll !important;

+}

+

+.form-buttons {

+    position:absolute;

+    bottom:0;

+    right:0; 

+    z-index:1000;

+    margin-bottom:-18px

+}

+

+.upload-progress {

+    position: absolute;

+    background-color: green;

+    height: 100%;

+    opacity: .5;

+    top: 0; 

+    left: 0;

+}

+app-form-generator textarea,

+app-form-generator input {

+    width: 100%;

+}

+

+.loader-modal-container .mat-dialog-container{

+    border-radius: 100px;

+    opacity: 0.1;

+}
\ 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
index 0000000..34bfd28
--- /dev/null
+++ b/otf-frontend/client/src/test.ts
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// This file is required by karma.conf.js and loads recursively all the .spec and framework files

+

+import 'zone.js/dist/long-stack-trace-zone';

+import 'zone.js/dist/proxy.js';

+import 'zone.js/dist/sync-test';

+import 'zone.js/dist/jasmine-patch';

+import 'zone.js/dist/async-test';

+import 'zone.js/dist/fake-async-test';

+import { getTestBed } from '@angular/core/testing';

+import {

+  BrowserDynamicTestingModule,

+  platformBrowserDynamicTesting

+} from '@angular/platform-browser-dynamic/testing';

+

+// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.

+declare const __karma__: any;

+declare const require: any;

+

+// Prevent Karma from running prematurely.

+__karma__.loaded = function () {};

+

+// First, initialize the Angular testing environment.

+getTestBed().initTestEnvironment(

+  BrowserDynamicTestingModule,

+  platformBrowserDynamicTesting()

+);

+// Then we find all the tests.

+const context = require.context('./', true, /\.spec\.ts$/);

+// And load the modules.

+context.keys().map(context);

+// Finally, start Karma to run the tests.

+__karma__.start();

diff --git a/otf-frontend/client/src/tsconfig.app.json b/otf-frontend/client/src/tsconfig.app.json
new file mode 100644
index 0000000..bdd3b1c
--- /dev/null
+++ b/otf-frontend/client/src/tsconfig.app.json
@@ -0,0 +1,20 @@
+{

+  "extends": "../config/tsconfig.json",

+  "compilerOptions": {

+    "outDir": "../out-tsc/app",

+    "baseUrl": "./",

+    "module": "es2015",

+    "types": [],

+    "paths": {

+      "fs": [

+        "./global-shims"

+      ]

+    },

+    "experimentalDecorators": true,

+    "allowJs": true

+  },

+  "exclude": [

+    "test.ts",

+    "**/*.spec.ts"

+  ]

+}
\ 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
index 0000000..e023398
--- /dev/null
+++ b/otf-frontend/client/src/tsconfig.json
@@ -0,0 +1,21 @@
+{

+    "extends": "../config/tsconfig.json",

+    "compilerOptions": {

+      "outDir": "../out-tsc/app",

+      "baseUrl": "./",

+      "module": "es2015",

+      "types": [],

+      "paths": {

+        "fs": [

+          "./global-shims"

+        ]

+      },

+      "experimentalDecorators": true,

+      "allowJs": true

+    },

+    "exclude": [

+      "test.ts",

+      "**/*.spec.ts"

+    ]

+  }

+  
\ 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
index 0000000..2046c9e
--- /dev/null
+++ b/otf-frontend/client/src/tsconfig.spec.json
@@ -0,0 +1,21 @@
+{

+  "extends": "../config/tsconfig.json",

+  "compilerOptions": {

+    "outDir": "../out-tsc/spec",

+    "baseUrl": "./",

+    "module": "commonjs",

+    "target": "es5",

+    "types": [

+      "jasmine",

+      "node"

+    ]

+  },

+  "files": [

+    "test.ts",

+    "polyfills.ts"

+  ],

+  "include": [

+    "**/*.spec.ts",

+    "**/*.d.ts"

+  ]

+}

diff --git a/otf-frontend/client/src/typings.d.ts b/otf-frontend/client/src/typings.d.ts
new file mode 100644
index 0000000..b179036
--- /dev/null
+++ b/otf-frontend/client/src/typings.d.ts
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+/* SystemJS module definition */

+declare var module: NodeModule;

+interface NodeModule {

+    id: string;

+}

+

+declare var fs: any;

+

+declare module "*.json" {

+    const value: any;

+    export default value;

+}
\ 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
index 0000000..2496e87
--- /dev/null
+++ b/otf-frontend/helm/otf-frontend/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1

+appVersion: "1.0"

+description: A Helm chart the OTF Frontend

+name: otf-frontend

+version: 0.0.5-SNAPSHOT

diff --git a/otf-frontend/helm/otf-frontend/templates/deployment.yaml b/otf-frontend/helm/otf-frontend/templates/deployment.yaml
new file mode 100644
index 0000000..4d6c3e8
--- /dev/null
+++ b/otf-frontend/helm/otf-frontend/templates/deployment.yaml
@@ -0,0 +1,218 @@
+apiVersion: extensions/v1beta1

+kind: Deployment

+metadata:

+  name: {{ .Values.appName}}

+  namespace: {{.Values.namespace}}

+  labels:

+    app: {{ .Values.appName}}

+    version: {{.Values.version}}

+spec:

+  revisionHistoryLimit: 1   # keep one replica set to allow rollback

+  minReadySeconds: 10

+  strategy:

+    # indicate which strategy we want for rolling update

+    type: RollingUpdate

+    rollingUpdate:

+      maxSurge: 1

+      maxUnavailable: 1

+  {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+  replicas: {{ .Values.replicas.prod}}

+  {{ else if  eq .Values.env "st"}}

+  replicas: {{ .Values.replicas.st}}

+  {{ else }}

+  replicas: {{ .Values.replicas.dev}}

+  {{ end }}

+  selector:

+    matchLabels:

+      app: {{ .Values.appName}}

+      version: {{.Values.version}}

+  template:

+    metadata:

+      labels:

+        app: {{ .Values.appName}}

+        version: {{.Values.version}}

+    spec:

+      serviceAccount: default

+      volumes:

+      - name: {{ .Values.appName}}-cert-volume

+        secret:

+          secretName: {{.Values.sharedCert}}

+          optional: true

+          items:

+          - key: PEM_CERT

+            path: otf.pem

+          - key: PEM_KEY

+            path: privateKey.pem

+      containers:

+      - name: {{ .Values.appName}}

+        image: {{ .Values.image}}

+        imagePullPolicy: Always

+        ports:

+        - name: https

+          containerPort: 443

+          nodePort: {{.Values.nodePort}}

+          protocol: TCP

+        {{ if eq .Values.env "st"}}

+        resources:

+          limits:

+            memory: "5Gi"

+            cpu: "3"

+          requests:

+            memory: "2Gi"

+            cpu: "1"

+        {{else}}

+        resources:

+          limits:

+            memory: "10Gi"

+            cpu: "6"

+          requests:

+            memory: "4Gi"

+            cpu: "2"

+        {{end}}

+        env:

+        - name: ENV

+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: "production"

+          {{ else if eq .Values.env "st" }}

+          value: "system_test"

+          {{ else }}

+          value: "development"

+          {{ end }}

+        - name: NAMESPACE

+          value: {{.Values.namespace}}

+        - name: APP_NAME

+          value: {{ .Values.appName}}

+        - name: APP_VERSION

+          value: {{.Values.version}}

+        - name: OTF_URL

+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{.Values.otf.OTF_URL.prod | quote}}

+          {{ else if eq .Values.env "st" }}

+          value: {{.Values.otf.OTF_URL.st | quote}}

+          {{ else }}

+          value: {{.Values.otf.OTF_URL.dev | quote}}

+          {{ end }}

+        - name: OTF_EMAIL

+          value: {{.Values.otf.OTF_EMAIL | quote}}

+        - name: AUTHENTICATION_SECRET

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.appName}}

+              key: authentication_secret

+              optional: true

+        - name: SERVICEAPI_URL

+          {{ if eq .Values.env "prod" }}

+          value: {{.Values.serviceApi.prod.SERVICEAPI_URL | quote}}

+          {{ else if eq .Values.env "prod-dr" }}

+          value: {{.Values.serviceApi.prod_dr.SERVICEAPI_URL | quote}}

+          {{ else if eq .Values.env "st" }}

+          value: {{.Values.serviceApi.st.SERVICEAPI_URL | quote}}

+          {{ else }}

+          value: {{.Values.serviceApi.dev.SERVICEAPI_URL | quote}}

+          {{ end }}

+        - name: SERVICEAPI_URIEXECUTETESTINSTANCE

+          {{ if eq .Values.env "prod" }}

+          value: {{.Values.serviceApi.prod.SERVICEAPI_URIEXECUTETESTINSTANCE | quote}}

+          {{ else if eq .Values.env "prod-dr" }}

+          value: {{.Values.serviceApi.prod_dr.SERVICEAPI_URIEXECUTETESTINSTANCE | quote}}          

+          {{ else if eq .Values.env "st" }}

+          value: {{.Values.serviceApi.st.SERVICEAPI_URIEXECUTETESTINSTANCE | quote}}

+          {{ else }}

+          value: {{.Values.serviceApi.dev.SERVICEAPI_URIEXECUTETESTINSTANCE | quote}}

+          {{ end }}

+        - name: SERVICEAPI_AAFID

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_id

+              optional: true

+        - name: SERVICEAPI_AAFPASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_mech_password

+              optional: true

+        - name: CAMUNDAAPI_URL

+          {{ if eq .Values.env "prod" }}

+          value: {{ .Values.camundaApi.prod.CAMUNDAAPI_URL | quote}}

+          {{ else if eq .Values.env "prod-dr" }}

+          value: {{ .Values.camundaApi.prod_dr.CAMUNDAAPI_URL | quote}}          

+          {{ else if eq .Values.env "st" }}

+          value: {{ .Values.camundaApi.st.CAMUNDAAPI_URL | quote}}

+          {{ else }}

+          value: {{ .Values.camundaApi.dev.CAMUNDAAPI_URL | quote}}

+          {{ end }}

+        - name: CAMUNDAAPI_AAFID

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_id

+              optional: true

+        - name: CAMUNDAAPI_AAFPASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_mech_password

+              optional: true

+        - name: MONGO_BASEURL

+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.mongo.prod.MONGO_BASEURL | quote}}

+          {{ else if eq .Values.env "st" }}

+          value: {{ .Values.mongo.st.MONGO_BASEURL | quote}}

+          {{ else }}

+          value: {{ .Values.mongo.dev.MONGO_BASEURL | quote}}

+          {{ end }}

+        - name: MONGO_DBOTF

+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.mongo.prod.MONGO_DBOTF | quote }}

+          {{ else if eq .Values.env "st" }}

+          value: {{ .Values.mongo.st.MONGO_DBOTF | quote }}

+          {{ else }}

+          value: {{ .Values.mongo.dev.MONGO_DBOTF | quote }}

+          {{ end }}

+        - name: MONGO_REPLICASET

+          {{ if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.mongo.prod.MONGO_REPLICASET | quote }}

+          {{ else if eq .Values.env "st" }}

+          value: {{ .Values.mongo.st.MONGO_REPLICASET | quote }}

+          {{ else }}

+          value: {{ .Values.mongo.dev.MONGO_REPLICASET | quote }}

+          {{ end }}

+        - name: MONGO_USERNAME

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.appName}}

+              key: mongo_username

+              optional: true

+        - name: MONGO_PASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.appName}}

+              key: mongo_password

+              optional: true

+        volumeMounts:

+        - name: {{.Values.appName}}-cert-volume

+          mountPath: /home/node/server/config/cert/

+        livenessProbe:

+          httpGet:

+            path: {{ .Values.healthEndpoint }}

+            port: https

+            scheme: HTTPS

+            httpHeaders:

+              - name: X-Custom-Header

+                value: Alive

+          initialDelaySeconds: 30

+          timeoutSeconds: 30

+          periodSeconds: 60

+        readinessProbe:

+          httpGet:

+            path: {{ .Values.healthEndpoint }}

+            port: https

+            scheme: HTTPS

+            httpHeaders:

+              - name: X-Custom-Header

+                value: Ready

+          initialDelaySeconds: 30

+          timeoutSeconds: 30

+          periodSeconds: 30

+      restartPolicy: Always

diff --git a/otf-frontend/helm/otf-frontend/templates/secret.yaml b/otf-frontend/helm/otf-frontend/templates/secret.yaml
new file mode 100644
index 0000000..175427c
--- /dev/null
+++ b/otf-frontend/helm/otf-frontend/templates/secret.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1

+kind: Secret

+metadata:

+  name: {{ .Values.appName}}

+type: Opaque

+data:

+  mongo_username: {{ .Values.mongo.username | b64enc}}

+  mongo_password: {{ .Values.mongo.password | b64enc}}

+  authentication_secret: {{.Values.AUTHENTICATION_SECRET | b64enc}}

diff --git a/otf-frontend/helm/otf-frontend/templates/service.yaml b/otf-frontend/helm/otf-frontend/templates/service.yaml
new file mode 100644
index 0000000..0b0badd
--- /dev/null
+++ b/otf-frontend/helm/otf-frontend/templates/service.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1

+kind: Service

+metadata:

+  name: {{ .Values.appName }}

+  namespace: {{ .Values.namespace }}

+  labels:

+    app: {{ .Values.appName }}

+    version: {{ .Values.version }}

+spec:

+  type: NodePort

+  ports:

+  - name: https

+    protocol: TCP

+    port: 443

+    nodePort: {{ .Values.nodePort }}

+  selector:

+    app: {{ .Values.appName }}

+    version: {{ .Values.version }}

diff --git a/otf-frontend/helm/otf-frontend/values.yaml b/otf-frontend/helm/otf-frontend/values.yaml
new file mode 100644
index 0000000..571d323
--- /dev/null
+++ b/otf-frontend/helm/otf-frontend/values.yaml
@@ -0,0 +1,59 @@
+appName: otf-frontend

+version: 0.0.4-SNAPSHOT

+image: otf-frontend

+namespace: 

+nodePort: 32524

+replicas:

+  dev: 2

+  st: 1

+  prod: 2

+env: dev

+AUTHENTICATION_SECRET: ""

+serviceApi:

+  prod:

+    SERVICEAPI_URL: "https://localhost:32303/otf/api/"

+    SERVICEAPI_URIEXECUTETESTINSTANCE: "testInstance/execute/v1/id/"

+  prod_dr:

+    SERVICEAPI_URL: "https://localhost:32303/otf/api/"

+    SERVICEAPI_URIEXECUTETESTINSTANCE: "testInstance/execute/v1/id/" 

+  st:

+    SERVICEAPI_URL: "https://localhost:32303/otf/api/"

+    SERVICEAPI_URIEXECUTETESTINSTANCE: "testInstance/execute/v1/id/"

+  dev:

+    SERVICEAPI_URL: "https://localhost:32303/otf/api/"

+    SERVICEAPI_URIEXECUTETESTINSTANCE: "testInstance/execute/v1/id/"

+camundaApi:

+  prod:

+    CAMUNDAAPI_URL: "https://localhost:31313/"

+  prod_dr:

+    CAMUNDAAPI_URL: "https://localhost:31313/"

+  st:

+    CAMUNDAAPI_URL: "https://localhost:31313/"

+  dev:

+    CAMUNDAAPI_URL: "https://localhost:31313/"

+mongo:

+  prod:

+    MONGO_BASEURL: "localhost:18720,localhost:18720,localhost:18720/"

+    MONGO_DBOTF: "otf"

+    MONGO_REPLICASET: "otf-rs-prod2"

+  st:

+    MONGO_BASEURL: "localhost:27017,localhost:27017,localhost:27017/"

+    MONGO_DBOTF: "otf_st"

+    MONGO_REPLICASET: "mongoOTF"

+  dev:

+    MONGO_BASEURL: "localhost:27017,localhost:27017,localhost:27017/"

+    MONGO_DBOTF: "otf"

+    MONGO_REPLICASET: "mongoOTF"

+  username: ""

+  password: ""

+otf:

+  OTF_EMAIL: "OTF_NO-REPLY@localhost.com"

+  OTF_URL:

+    dev: "https://localhost:32524/"

+    st: "https://localhost:32524/"

+    prod: "https://localhost:32524/"

+

+sharedSecret: otf-aaf-credential-generator

+sharedCert: otf-cert-secret-builder

+

+healthEndpoint: otf/api/health/v1

diff --git a/otf-frontend/package.json b/otf-frontend/package.json
new file mode 100644
index 0000000..84fa16d
--- /dev/null
+++ b/otf-frontend/package.json
@@ -0,0 +1,168 @@
+{

+  "name": "otf-frontend",

+  "description": "This module is made up of the OTF User Interface, and a Node.js server to serve it.",

+  "keywords": [

+    "otf",

+    "open testing framework",

+    "best ui"

+  ],

+  "bugs": "lol",

+  "licenses": [

+    "TBD"

+  ],

+  "authors": "Raj Patel",

+  "contributors": [

+    "Justin Meiliinger, Adam Ordway, Raj Patel, Rohan Patel"

+  ],

+  "version": "1.0.0a",

+  "scripts": {

+    "ng": "ng",

+    "start": "node server/src/app.js",

+    "debug": "node server/src/app.js",

+    "build": "ng build --prod --output-path ./client/dist/ --build-optimizer=false",

+    "gitbuild": "ng build --prod --base-href /start-angular/SB-Admin-BS4-Angular-6/master/dist/",

+    "test": "ng test",

+    "test-ci": "TEST_CI=true ng test",

+    "lint": "ng lint",

+    "e2e": "ng e2e",

+    "postinstall": "ng add ng-cli-pug-loader@0.1.7 && node ./ng-add-pug-loader.js"

+  },

+  "directories": {

+    "lib": "server/src/feathers"

+  },

+  "private": true,

+  "dependencies": {

+    "@amcharts/amcharts4": "^4.5.3",

+    "@angular/animations": "^6.1.7",

+    "@angular/cdk": "^6.4.7",

+    "@angular/common": "^6.1.7",

+    "@angular/compiler": "^6.1.7",

+    "@angular/core": "^6.1.7",

+    "@angular/forms": "^6.1.7",

+    "@angular/http": "^6.1.7",

+    "@angular/material": "^6.4.7",

+    "@angular/platform-browser": "^7.0.2",

+    "@angular/platform-browser-dynamic": "^7.0.2",

+    "@angular/router": "^6.1.7",

+    "@casl/ability": "^3.1.2",

+    "@casl/angular": "^2.1.0",

+    "@casl/mongoose": "^2.3.1",

+    "@feathersjs/authentication": "^2.1.13",

+    "@feathersjs/authentication-client": "^1.0.11",

+    "@feathersjs/authentication-jwt": "^2.0.7",

+    "@feathersjs/authentication-local": "^1.2.7",

+    "@feathersjs/client": "^3.7.8",

+    "@feathersjs/configuration": "^1.0.2",

+    "@feathersjs/errors": "^3.3.0",

+    "@feathersjs/express": "^1.2.3",

+    "@feathersjs/feathers": "^3.1.7",

+    "@feathersjs/socketio": "^3.2.7",

+    "@feathersjs/socketio-client": "^1.2.1",

+    "@ng-bootstrap/ng-bootstrap": "^2.0.0",

+    "@ngx-translate/core": "^10.0.1",

+    "@ngx-translate/http-loader": "^3.0.1",

+    "@types/socket.io-client": "^1.4.32",

+    "ag-grid-angular": "^20.2.0",

+    "ag-grid-community": "^20.2.0",

+    "agenda": "^2.0.2",

+    "angular-datatables": "^6.0.0",

+    "angular-particle": "^1.0.4",

+    "angular-resizable-element": "^3.2.4",

+    "axios": "^0.19.0",

+    "bootstrap": "^4.3.1",

+    "bpmn-font": "^0.8.0",

+    "bpmn-js": "^2.5.2",

+    "bpmn-js-properties-panel": "^0.32.1",

+    "btoa": "^1.2.1",

+    "camunda-bpmn-moddle": "^3.2.0",

+    "classlist.js": "^1.1.20150312",

+    "clean": "^4.0.2",

+    "codemirror": "^5.41.0",

+    "cors": "^2.8.5",

+    "datatables.net": "^1.10.19",

+    "datatables.net-dt": "^1.10.19",

+    "diagram-js-minimap": "^1.3.0",

+    "dot-object": "^1.9.0",

+    "express-rate-limit": "^3.3.2",

+    "feathers-authentication-management": "^2.0.1",

+    "feathers-hooks-common": "^4.17.14",

+    "feathers-mongoose": "^6.2.0",

+    "feathers-permissions": "^0.2.1",

+    "file-saver": "^2.0.1",

+    "font-awesome": "^4.7.0",

+    "helmet": "^3.14.0",

+    "http-response-object": "^3.0.1",

+    "jquery": "^3.4.1",

+    "json-beautify": "^1.0.1",

+    "jsonbeautify": "0.0.1",

+    "lodash.pick": "^4.4.0",

+    "mat-progress-buttons": "^7.0.10",

+    "material-design-icons": "^3.0.1",

+    "moment": "^2.22.2",

+    "mongoose": "^5.6.4",

+    "mongoose-gridfs": "^0.5.0",

+    "multer": "^1.4.1",

+    "ng-cli-pug-loader": "^0.1.7",

+    "ng2-codemirror": "^1.1.3",

+    "ng2-completer": "^2.0.8",

+    "ng2-file-upload": "^1.3.0",

+    "ngx-cookie-service": "^2.0.0",

+    "ngx-filter-pipe": "^2.1.2",

+    "ngx-json-viewer": "^2.4.0",

+    "ngx-material-timepicker": "^2.8.4",

+    "ngx-perfect-scrollbar": "^7.0.0",

+    "ngx-socket-io": "^2.1.1",

+    "npm": "^6.10.1",

+    "object.pick": "^1.3.0",

+    "pickle-rick": "^0.1.0",

+    "rate-limit-mongo": "^1.0.3",

+    "redis": "^2.8.0",

+    "rxjs-compat": "^6.4.0",

+    "sendmail": "^1.4.1",

+    "serve-favicon": "^2.5.0",

+    "socket.io-client": "^2.2.0",

+    "unzip-stream": "^0.3.0",

+    "update": "^0.7.4",

+    "uuid": "^3.3.2",

+    "web-animations-js": "^2.3.1",

+    "winston": "^3.0.0",

+    "xml2js": "^0.4.19",

+    "yamljs": "^0.3.0",

+    "zone.js": "^0.8.26"

+  },

+  "devDependencies": {

+    "@angular-devkit/build-angular": "^0.6.8",

+    "@angular/cli": "^6.2.7",

+    "@angular/compiler-cli": "^6.1.7",

+    "@angular/language-service": "^6.1.7",

+    "@types/datatables.net": "^1.10.16",

+    "@types/jasmine": "^2.8.11",

+    "@types/jasminewd2": "^2.0.6",

+    "@types/jquery": "^3.3.29",

+    "@types/node": "^9.6.52",

+    "apply-loader": "^2.0.0",

+    "codelyzer": "~4.2.1",

+    "eslint": "^5.8.0",

+    "eslint-plugin-import": "^2.14.0",

+    "eslint-plugin-node": "^7.0.1",

+    "eslint-plugin-promise": "^4.0.1",

+    "eslint-plugin-standard": "^4.0.0",

+    "jasmine-core": "^3.3.0",

+    "jasmine-spec-reporter": "~4.2.1",

+    "karma": "~2.0.0",

+    "karma-chrome-launcher": "~2.2.0",

+    "karma-cli": "~1.0.1",

+    "karma-coverage-istanbul-reporter": "^1.4.2",

+    "karma-jasmine": "~1.1.1",

+    "karma-jasmine-html-reporter": "^1.4.0",

+    "mocha": "^5.2.0",

+    "protractor": "^5.4.2",

+    "pug": "^2.0.4",

+    "pug-loader": "^2.4.0",

+    "request": "^2.88.0",

+    "request-promise": "^4.2.2",

+    "ts-node": "~5.0.1",

+    "tslint": "~5.9.1",

+    "typescript": "~2.8.0"

+  }

+}

diff --git a/otf-frontend/server/config/.eslintrc b/otf-frontend/server/config/.eslintrc
new file mode 100644
index 0000000..2377cf4
--- /dev/null
+++ b/otf-frontend/server/config/.eslintrc
@@ -0,0 +1,474 @@
+{

+  "parserOptions": {

+    "ecmaVersion": 2018,

+    "ecmaFeatures": {

+      "jsx": true

+    },

+    "sourceType": "module"

+  },

+  "env": {

+    "es6": true,

+    "node": true

+  },

+  "plugins": [

+    "import",

+    "node",

+    "promise",

+    "standard"

+  ],

+  "globals": {

+    "document": false,

+    "navigator": false,

+    "window": false

+  },

+  "rules": {

+    "accessor-pairs": "error",

+    "arrow-spacing": [

+      "error",

+      {

+        "before": true,

+        "after": true

+      }

+    ],

+    "block-spacing": [

+      "error",

+      "always"

+    ],

+    "brace-style": [

+      "error",

+      "1tbs",

+      {

+        "allowSingleLine": true

+      }

+    ],

+    "camelcase": [

+      "error",

+      {

+        "properties": "never"

+      }

+    ],

+    "comma-dangle": [

+      "error",

+      {

+        "arrays": "never",

+        "objects": "never",

+        "imports": "never",

+        "exports": "never",

+        "functions": "never"

+      }

+    ],

+    "comma-spacing": [

+      "error",

+      {

+        "before": false,

+        "after": true

+      }

+    ],

+    "comma-style": [

+      "error",

+      "last"

+    ],

+    "constructor-super": "error",

+    "curly": [

+      "error",

+      "multi-line"

+    ],

+    "dot-location": [

+      "error",

+      "property"

+    ],

+    "eol-last": "error",

+    "eqeqeq": [

+      "error",

+      "always",

+      {

+        "null": "ignore"

+      }

+    ],

+    "func-call-spacing": [

+      "error",

+      "never"

+    ],

+    "generator-star-spacing": [

+      "error",

+      {

+        "before": true,

+        "after": true

+      }

+    ],

+    "handle-callback-err": [

+      "error",

+      "^(err|error)$"

+    ],

+    "indent": [

+      "error",

+      "tab",

+      {

+        "SwitchCase": 1,

+        "VariableDeclarator": 1,

+        "outerIIFEBody": 1,

+        "MemberExpression": 1,

+        "FunctionDeclaration": {

+          "parameters": 1,

+          "body": 1

+        },

+        "FunctionExpression": {

+          "parameters": 1,

+          "body": 1

+        },

+        "CallExpression": {

+          "arguments": 1

+        },

+        "ArrayExpression": 1,

+        "ObjectExpression": 1,

+        "ImportDeclaration": 1,

+        "flatTernaryExpressions": false,

+        "ignoreComments": false

+      }

+    ],

+    "key-spacing": [

+      "error",

+      {

+        "beforeColon": false,

+        "afterColon": true

+      }

+    ],

+    "keyword-spacing": [

+      "error",

+      {

+        "before": true,

+        "after": true

+      }

+    ],

+    "new-cap": [

+      "error",

+      {

+        "newIsCap": true,

+        "capIsNew": false

+      }

+    ],

+    "new-parens": "error",

+    "no-array-constructor": "error",

+    "no-caller": "error",

+    "no-class-assign": "error",

+    "no-compare-neg-zero": "error",

+    "no-cond-assign": "error",

+    "no-const-assign": "error",

+    "no-constant-condition": [

+      "error",

+      {

+        "checkLoops": false

+      }

+    ],

+    "no-control-regex": "error",

+    "no-debugger": "error",

+    "no-delete-var": "error",

+    "no-dupe-args": "error",

+    "no-dupe-class-members": "error",

+    "no-dupe-keys": "error",

+    "no-duplicate-case": "error",

+    "no-empty-character-class": "error",

+    "no-empty-pattern": "error",

+    "no-eval": "error",

+    "no-ex-assign": "error",

+    "no-extend-native": "error",

+    "no-extra-bind": "error",

+    "no-extra-boolean-cast": "error",

+    "no-extra-parens": [

+      "error",

+      "functions"

+    ],

+    "no-fallthrough": "error",

+    "no-floating-decimal": "error",

+    "no-func-assign": "error",

+    "no-global-assign": "error",

+    "no-implied-eval": "error",

+    "no-inner-declarations": [

+      "error",

+      "functions"

+    ],

+    "no-invalid-regexp": "error",

+    "no-irregular-whitespace": "error",

+    "no-iterator": "error",

+    "no-label-var": "error",

+    "no-labels": [

+      "error",

+      {

+        "allowLoop": false,

+        "allowSwitch": false

+      }

+    ],

+    "no-lone-blocks": "error",

+    "no-mixed-operators": [

+      "error",

+      {

+        "groups": [

+          [

+            "==",

+            "!=",

+            "===",

+            "!==",

+            ">",

+            ">=",

+            "<",

+            "<="

+          ],

+          [

+            "&&",

+            "||"

+          ],

+          [

+            "in",

+            "instanceof"

+          ]

+        ],

+        "allowSamePrecedence": true

+      }

+    ],

+    "no-mixed-spaces-and-tabs": "error",

+    "no-multi-spaces": "error",

+    "no-multi-str": "error",

+    "no-multiple-empty-lines": [

+      "error",

+      {

+        "max": 1,

+        "maxEOF": 0

+      }

+    ],

+    "no-negated-in-lhs": "error",

+    "no-new": "error",

+    "no-new-func": "error",

+    "no-new-object": "error",

+    "no-new-require": "error",

+    "no-new-symbol": "error",

+    "no-new-wrappers": "error",

+    "no-obj-calls": "error",

+    "no-octal": "error",

+    "no-octal-escape": "error",

+    "no-path-concat": "error",

+    "no-proto": "error",

+    "no-redeclare": "error",

+    "no-regex-spaces": "error",

+    "no-return-assign": [

+      "error",

+      "except-parens"

+    ],

+    "no-return-await": "error",

+    "no-self-assign": "error",

+    "no-self-compare": "error",

+    "no-sequences": "error",

+    "no-shadow-restricted-names": "error",

+    "no-sparse-arrays": "error",

+    "no-template-curly-in-string": "error",

+    "no-this-before-super": "error",

+    "no-trailing-spaces": "error",

+    "no-undef": "error",

+    "no-undef-init": "error",

+    "no-unexpected-multiline": "error",

+    "no-unmodified-loop-condition": "error",

+    "no-unneeded-ternary": [

+      "error",

+      {

+        "defaultAssignment": false

+      }

+    ],

+    "no-unreachable": "error",

+    "no-unsafe-finally": "error",

+    "no-unsafe-negation": "error",

+    "no-unused-expressions": [

+      "error",

+      {

+        "allowShortCircuit": true,

+        "allowTernary": true,

+        "allowTaggedTemplates": true

+      }

+    ],

+    "no-unused-vars": [

+      "error",

+      {

+        "vars": "all",

+        "args": "none",

+        "ignoreRestSiblings": true

+      }

+    ],

+    "no-use-before-define": [

+      "error",

+      {

+        "functions": false,

+        "classes": false,

+        "variables": false

+      }

+    ],

+    "no-useless-call": "error",

+    "no-useless-computed-key": "error",

+    "no-useless-constructor": "error",

+    "no-useless-escape": "error",

+    "no-useless-rename": "error",

+    "no-useless-return": "error",

+    "no-whitespace-before-property": "error",

+    "no-with": "error",

+    "object-curly-spacing": [

+      "error",

+      "always"

+    ],

+    "object-property-newline": [

+      "error",

+      {

+        "allowMultiplePropertiesPerLine": true

+      }

+    ],

+    "one-var": [

+      "error",

+      {

+        "initialized": "never"

+      }

+    ],

+    "operator-linebreak": [

+      "error",

+      "after",

+      {

+        "overrides": {

+          "?": "before",

+          ":": "before"

+        }

+      }

+    ],

+    "padded-blocks": [

+      "error",

+      {

+        "blocks": "never",

+        "switches": "never",

+        "classes": "never"

+      }

+    ],

+    "prefer-promise-reject-errors": "error",

+    "quotes": [

+      "error",

+      "single",

+      {

+        "avoidEscape": true,

+        "allowTemplateLiterals": true

+      }

+    ],

+    "rest-spread-spacing": [

+      "error",

+      "never"

+    ],

+    "semi": [

+      "error",

+      "always"

+    ],

+    "semi-spacing": [

+      "error",

+      {

+        "before": false,

+        "after": true

+      }

+    ],

+    "space-before-blocks": [

+      "error",

+      "always"

+    ],

+    "space-before-function-paren": [

+      "error",

+      "always"

+    ],

+    "space-in-parens": [

+      "error",

+      "never"

+    ],

+    "space-infix-ops": "error",

+    "space-unary-ops": [

+      "error",

+      {

+        "words": true,

+        "nonwords": false

+      }

+    ],

+    "spaced-comment": [

+      "error",

+      "always",

+      {

+        "line": {

+          "markers": [

+            "*package",

+            "!",

+            "/",

+            ",",

+            "="

+          ]

+        },

+        "block": {

+          "balanced": true,

+          "markers": [

+            "*package",

+            "!",

+            ",",

+            ":",

+            "::",

+            "flow-include"

+          ],

+          "exceptions": [

+            "*"

+          ]

+        }

+      }

+    ],

+    "symbol-description": "error",

+    "template-curly-spacing": [

+      "error",

+      "never"

+    ],

+    "template-tag-spacing": [

+      "error",

+      "never"

+    ],

+    "unicode-bom": [

+      "error",

+      "never"

+    ],

+    "use-isnan": "error",

+    "valid-typeof": [

+      "error",

+      {

+        "requireStringLiterals": true

+      }

+    ],

+    "wrap-iife": [

+      "error",

+      "any",

+      {

+        "functionPrototypeMethods": true

+      }

+    ],

+    "yield-star-spacing": [

+      "error",

+      "both"

+    ],

+    "yoda": [

+      "error",

+      "never"

+    ],

+    "import/export": "error",

+    "import/first": "error",

+    "import/no-duplicates": "error",

+    "import/no-named-default": "error",

+    "import/no-webpack-loader-syntax": "error",

+    "node/no-deprecated-api": "error",

+    "node/process-exit-as-throw": "error",

+    "promise/param-names": "error",

+    "standard/array-bracket-even-spacing": [

+      "error",

+      "either"

+    ],

+    "standard/computed-property-even-spacing": [

+      "error",

+      "even"

+    ],

+    "standard/no-callback-literal": "error",

+    "standard/object-curly-even-spacing": [

+      "error",

+      "either"

+    ]

+  }

+}
\ 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
index 0000000..0799b0b
--- /dev/null
+++ b/otf-frontend/server/config/cert/info.txt
@@ -0,0 +1 @@
+add certs here. requires two pem files (Cert and key)

diff --git a/otf-frontend/server/config/custom-environment-variables.json b/otf-frontend/server/config/custom-environment-variables.json
new file mode 100644
index 0000000..a261f9a
--- /dev/null
+++ b/otf-frontend/server/config/custom-environment-variables.json
@@ -0,0 +1,29 @@
+{

+    "authentication": {

+        "secret": "AUTHENTICATION_SECRET"

+    },

+    "serviceApi": {

+        "url": "SERVICEAPI_URL",

+        "uriExecuteTestInstance": "SERVICEAPI_URIEXECUTETESTINSTANCE",

+        "aafId": "SERVICEAPI_AAFID",

+        "aafPassword": "SERVICEAPI_AAFPASSWORD"

+    },

+    "camundaApi": {

+        "url": "CAMUNDAAPI_URL",

+        "aafId": "CAMUNDAAPI_AAFID",

+        "aafPassword": "CAMUNDAAPI_AAFPASSWORD"

+    },

+    "mongo": {

+        "baseUrl": "MONGO_BASEURL",

+        "dbOtf": "MONGO_DBOTF",

+        "replicaSet": "MONGO_REPLICASET",

+        "username": "MONGO_USERNAME",

+        "password": "MONGO_PASSWORD"

+    },

+    "otf": {

+      "url" : "OTF_URL",

+      "email" : "OTF_EMAIL"

+    },

+    "env": "ENV"

+    

+}

diff --git a/otf-frontend/server/config/production.json b/otf-frontend/server/config/production.json
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/otf-frontend/server/config/production.json
diff --git a/otf-frontend/server/src/agenda/agenda.js b/otf-frontend/server/src/agenda/agenda.js
new file mode 100644
index 0000000..5094b6b
--- /dev/null
+++ b/otf-frontend/server/src/agenda/agenda.js
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const logger = require('../lib/logger');

+const Agenda = require('agenda');

+const mongoData = require('config').mongo;

+const jobTypes = ['test-execution-job'];

+const agenda = new Agenda({

+	db: {

+		address: 'mongodb://' + mongoData.username + ':' + mongoData.password + '@' + mongoData.baseUrl + mongoData.dbOtf + '?replicaSet=' + mongoData.replicaSet,

+		collection: 'agenda'

+	}

+});

+

+module.exports = {

+	agenda: agenda,

+	initializeAgenda: function () {

+		// Load all job types

+		jobTypes.forEach(type => {

+			require('./jobs/' + type)(agenda);

+		});

+

+		// Wait for the db connection to be established before starting agenda (sync).

+		agenda.on('ready', function () {

+			logger.debug('Agenda successfully established a connection to MongoDB.');

+			agenda.start();

+			// agenda.processEvery('0.001 seconds');

+		});

+

+		async function graceful () {

+			await agenda.stop();

+			process.exit(0);

+		}

+

+		process.on('SIGTERM', graceful);

+		process.on('SIGINT', graceful);

+	}

+};

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
index 0000000..56f07a2
--- /dev/null
+++ b/otf-frontend/server/src/agenda/controllers/test-execution-controller.js
@@ -0,0 +1,220 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const logger = require('../../lib/logger');

+const agenda = require('../agenda').agenda;

+const emitter = require('../result-emitter').emitter;

+const utils = require('../../lib/otf-util');

+const nodeUtil = require('util');

+

+const ObjectId = require('mongoose').Types.ObjectId;

+

+const TestSchedule = require('../models/test-schedule');

+

+const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;

+

+module.exports = function (app) {

+	let scheduleTestResponse = { status: '', message: '' };

+	let cancelTestResponse = { status: '', message: '' };

+

+	// Main endpoint for scheduling

+	app.use('/' + app.get('base-path') + 'schedule-test', (req, res, next) => {

+		const authorizationHeader = req.headers.authorization;

+

+		const testInstanceId = req.body.testInstanceId;

+		const testInstanceStartDate = req.body.testInstanceStartDate;

+		const testInstanceExecFreqInSeconds = req.body.testInstanceExecFreqInSeconds;

+		const testInstanceEndDate = req.body.testInstanceEndDate;

+		const async = req.body.async;

+		const asyncTopic = req.body.asyncTopic;

+		const asyncMode = req.body.asyncMode;

+		const executorId = req.body.executorId;

+

+		let testSchedule = null;

+

+		try {

+			testSchedule = new TestSchedule(testInstanceId, testInstanceStartDate, testInstanceExecFreqInSeconds,

+				testInstanceEndDate, async, asyncTopic, asyncMode, executorId);

+		} catch (error) {

+			scheduleTestResponse.status = 400;

+			scheduleTestResponse.message = error.toString();

+			next();

+

+			return;

+		}

+

+		// The presence of this parameter indicates that we will be executing either job definition 2/3.

+		if (testSchedule.testInstanceStartDate) {

+			if (testSchedule.testInstanceExecFreqInSeconds) {

+				const job = agenda.create(

+					'executeTestAsync',

+					{ testSchedule, authorizationHeader });

+				job.schedule(testSchedule.testInstanceStartDate).repeatEvery(testSchedule.testInstanceExecFreqInSeconds + ' seconds', {

+					timezone: timeZone

+				});

+				job.save().then(function onJobCreated (result) {

+						logger.debug(JSON.stringify(result));

+						scheduleTestResponse.status = 200;

+						scheduleTestResponse.message = 'Successfully scheduled job.';

+						next();

+					})

+					.catch(function onError (error) {

+						logger.error(error);

+						scheduleTestResponse.status = 500;

+						scheduleTestResponse.message = 'Unable to schedule job.';

+						next();

+					});

+			} else if (!testSchedule.testInstanceExecFreqInSeconds && !testSchedule.testInstanceEndDate) {

+				agenda.schedule(

+					testSchedule._testInstanceStartDate,

+					'executeTestAsync',

+					{ testSchedule, authorizationHeader })

+					.then(function onJobCreated (result) {

+						scheduleTestResponse.status = 200;

+						scheduleTestResponse.message = 'Successfully scheduled job.';

+						next();

+					})

+					.catch(function onError (error) {

+						logger.error('error: ' + error);

+						scheduleTestResponse.status = 500;

+						scheduleTestResponse.message = 'Unable to schedule job.';

+						next();

+					});

+				return;

+			} else if (testSchedule.testInstanceEndDate && !testSchedule.testInstanceExecFreqInSeconds) {

+				scheduleTestResponse.status = 400;

+				scheduleTestResponse.message = 'Must specify \'testInstanceExecFreqInSeconds\' to use \'testInstanceEndDate\'';

+

+				next();

+			}

+		}

+

+		if (!testSchedule.testInstanceStartDate &&

+			!testSchedule.testInstanceExecFreqInSeconds &&

+			!testSchedule.testInstanceExecFreqInSeconds) {

+			agenda.now(

+				'executeTestSync',

+				{ testSchedule, authorizationHeader })

+				.then(function onJobCreated (result) {

+					emitter.once(result.attrs._id + '_error', (res) => {

+						logger.info(res);

+						scheduleTestResponse.message = res.message;

+						scheduleTestResponse.status = res.statusCode;

+						next();

+					});

+

+					emitter.once(result.attrs._id + '_ok', (res) => {

+						logger.info(res);

+						scheduleTestResponse.message = res;

+						scheduleTestResponse.status = 200;

+						next();

+					});

+				})

+				.catch(function onError (err) {

+					logger.error(err);

+

+					if (!Object.keys(scheduleTestResponse).includes('message')) {

+						scheduleTestResponse.message = 'Unknown error.';

+					}

+

+					if (!Object.keys(scheduleTestResponse).includes('status')) {

+						scheduleTestResponse.status = 500;

+					}

+				});

+		}

+	}, (req, res) => {

+		res.type('json');

+		res.status(scheduleTestResponse.status).send(scheduleTestResponse);

+		logger.debug('Sent response with status %d and body %s', scheduleTestResponse.status, scheduleTestResponse.message);

+	});

+

+	// Cancel

+	app.use('/' + app.get('base-path') + 'cancel-test', (req, res, next) => {

+		// validate the request parameters

+		if (req.body === null) {

+			cancelTestResponse.status = 400;

+			cancelTestResponse.message = 'Request data is invalid.';

+

+			next();

+			return;

+		}

+

+		let requestBody = req.body;

+

+		if (!requestBody.jobId) {

+			cancelTestResponse.status = 400;

+			cancelTestResponse.message = 'jobId is required.';

+

+			next();

+			return;

+		}

+

+		if (!utils.isValidObjectId(requestBody.jobId)) {

+			cancelTestResponse.status = 400;

+			cancelTestResponse.message = 'jobId must be a valid ObjectId.';

+

+			next();

+			return;

+		}

+

+		if (!requestBody.executorId) {

+			cancelTestResponse.status = 400;

+			cancelTestResponse.message = 'executorId is required.';

+

+			next();

+			return;

+		}

+

+		if (!utils.isValidObjectId(requestBody.executorId)) {

+			cancelTestResponse.status = 400;

+			cancelTestResponse.message = 'executorId must be a valid ObjectId.';

+

+			next();

+			return;

+		}

+

+		const jobId = new ObjectId(requestBody.jobId);

+		const executorId = new ObjectId(requestBody.executorId);

+

+		agenda.cancel({ _id: jobId, 'data.testSchedule._executorId': executorId })

+			.then(function onJobRemoved (numJobsRemoved) {

+				logger.info('Number of jobs removed: %s', numJobsRemoved);

+

+				cancelTestResponse.status = 200;

+				cancelTestResponse.message = nodeUtil.format('Successfully removed job with Id %s', jobId);

+

+				if (numJobsRemoved === 0) {

+					cancelTestResponse.status = 500;

+					cancelTestResponse.message =

+						nodeUtil.format('Unable to find job with Id %s, belonging to user with Id %s.', jobId, executorId);

+				}

+

+				next();

+			})

+			.catch(function onError (error) {

+				logger.error(error.toString());

+

+				cancelTestResponse.status = 500;

+				cancelTestResponse.message = 'Unable to cancel the job due to an unexpected error.';

+

+				next();

+			});

+	}, (req, res) => {

+		res.type('json');

+		res.status(cancelTestResponse.status).send(cancelTestResponse);

+		logger.debug('Sent response with status %d and body %s', cancelTestResponse.status, cancelTestResponse.message);

+	});

+};

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
index 0000000..b9a9198
--- /dev/null
+++ b/otf-frontend/server/src/agenda/jobs/test-execution-job.js
@@ -0,0 +1,133 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const request = require('request');

+const requestPromise = require('request-promise');

+const logger = require('../../lib/logger');

+const emitter = require('../result-emitter').emitter;

+const config = require('config');

+

+const loggerTagExecuteTestSync = '[JOB-sync] ';

+const loggerTagExecuteTestAsync = '[JOB-async] ';

+

+module.exports = function (agenda) {

+	// [Job Definition] : Executes a testInstance synchronously.

+	agenda.define('executeTestSync', (job) => {

+		logger.debug(loggerTagExecuteTestSync + 'Running job %s.', job.attrs._id);

+

+		// Extact the testSchedule from the job data.

+		const testSchedule = job.attrs.data.testSchedule;

+

+		logger.debug('[POST-' +

+			config.serviceApi.url + config.serviceApi.uriExecuteTestInstance + testSchedule._testInstanceId + ']');

+

+		// Create and send the request

+		requestPromise.post({

+			url: config.serviceApi.url + config.serviceApi.uriExecuteTestInstance + testSchedule._testInstanceId,

+			headers: {

+				'Authorization': job.attrs.data.authorizationHeader,

+				'Content-Type': 'application/json'

+			},

+			body: {

+				'async': false,

+				'executorId': testSchedule._executorId

+			},

+			json: true

+		}, function onResponseOk(response) {

+			logger.debug('[POST-ok]: ' + JSON.stringify(response));

+			emitter.emit(job.attrs._id + '_ok', response);

+		}, function onResponseError(response) {

+			logger.debug('[POST-error]: ' + JSON.stringify(response));

+			emitter.emit(job.attrs._id + '_error', response);

+		});

+	});

+

+	// [Job Definition] : Executes a testInstance asynchronously.

+	agenda.define('executeTestAsync', (job, done) => {

+		logger.debug(loggerTagExecuteTestAsync + 'Running job %s.', job.attrs._id);

+

+		// Extact the testSchedule from the job data.

+		const testSchedule = job.attrs.data.testSchedule;

+

+		if (testSchedule._testInstanceEndDate) {

+			const currentDate = Date.now().valueOf();

+			const endDate = Date.parse(testSchedule._testInstanceEndDate).valueOf();

+

+			if (currentDate >= endDate) {

+				job.remove(err => {

+					if (!err) {

+						logger.debug('Job %s finished.', job.attrs._id);

+					} else {

+						logger.error(err);

+					}

+				});

+

+				done();

+				return;

+			}

+		}

+

+		logger.debug('[POST-%s]', config.serviceApi.url + config.serviceApi.uriExecuteTestInstance + testSchedule._testInstanceId);

+

+		// Create and send the request (we don't care about the response)

+		request.post({

+			url: config.serviceApi.url + config.serviceApi.uriExecuteTestInstance + testSchedule._testInstanceId,

+			headers: {

+				'Authorization': job.attrs.data.authorizationHeader,

+				'Content-Type': 'application/json'

+			},

+			body: {

+				'async': true,

+				'executorId': testSchedule._executorId

+			},

+			json: true

+		}, function onResponseOk(response) {

+			logger.debug('[POST-ok]: ' + JSON.stringify(response));

+			emitter.emit(job.attrs._id + '_ok', response);

+		}, function onResponseError(response) {

+			logger.debug('[POST-error]: ' + JSON.stringify(response));

+			emitter.emit(job.attrs._id + '_error', response);

+		});

+

+		done();

+	});

+

+	agenda.define('executeTestOnInterval', (job, done) => {

+		logger.debug('[JOB-executeTestOnInterval] running...');

+

+		// Extact the testSchedule from the job data.

+		const testSchedule = job.attrs.data.testSchedule;

+

+		if (testSchedule._testInstanceEndDate) {

+			if (new Date().now() > testSchedule._testInstanceEndDate) {

+				job.remove((err) => {

+					if (err) {

+						logger.error(err); 

+					}

+				});

+			}

+		}

+

+		logger.info('exec freq ' + testSchedule.testInstanceExecFreqInSeconds());

+

+		agenda.every(

+			testSchedule._testInstanceExecFreqInSeconds + ' seconds',

+			'executeTestAsync',

+			{testSchedule: job.attrs.data.testSchedule, authorizationHeader: job.attrs.data.authorizationHeader});

+

+		done();

+	});

+};

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
index 0000000..6494695
--- /dev/null
+++ b/otf-frontend/server/src/agenda/models/test-schedule.js
@@ -0,0 +1,154 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const logger = require('../../lib/logger');

+const utils = require('../../lib/otf-util');

+const ObjectId = require('mongoose').Types.ObjectId;

+

+class TestSchedule {

+	constructor (testInstanceId, testInstanceStartDate, testInstanceExecFreqInSeconds, testInstanceEndDate,

+	             async, asyncTopic, asyncMode, executorId) {

+		this.testInstanceId = testInstanceId;

+		this.testInstanceStartDate = testInstanceStartDate;

+		this.testInstanceExecFreqInSeconds = testInstanceExecFreqInSeconds;

+		this.testInstanceEndDate = testInstanceEndDate;

+		this.async = async;

+		this.asyncTopic = asyncTopic;

+		this.asyncMode = asyncMode;

+		this.executorId = executorId;

+	}

+

+	get testInstanceId () {

+		return this._testInstanceId;

+	}

+

+	set testInstanceId (value) {

+		if (!value) {

+			throw 'testInstanceId is required.';

+		}

+

+		if (!utils.isValidObjectId(value)) {

+			throw 'testInstanceId must be a valid ObjectId';

+		}

+

+		this._testInstanceId = new ObjectId(value);

+	}

+

+	get testInstanceStartDate () {

+		return this._testInstanceStartDate;

+	}

+

+	set testInstanceStartDate (value) {

+		// Accepts type Date, and the "now" keyword recognized by human interval (integrated with Agenda)

+		if (value !== 'now') {

+			let parsedDate = Date.parse(value);

+

+			if (isNaN((parsedDate))) {

+				throw 'testInstanceStartDate must be a valid date, or must be ' / 'now' / '.';

+			}

+		}

+

+		this._testInstanceStartDate = value;

+	}

+

+	get testInstanceExecFreqInSeconds () {

+		return this._testInstanceExecFreqInSeconds;

+	}

+

+	set testInstanceExecFreqInSeconds (value) {

+		if (value) {

+			if (typeof value !== 'number') {

+				throw 'testInstanceExecFreqInSeconds must be a number.';

+			}

+

+			if (value < 30) {

+				throw 'testInstanceExecFreqInSeconds must be greater than or equal to 30.';

+			}

+		}

+

+		this._testInstanceExecFreqInSeconds = value;

+	}

+

+	get testInstanceEndDate () {

+		return this._testInstanceEndDate;

+	}

+

+	set testInstanceEndDate (value) {

+		// Allow a null end date

+		if (value) {

+			let parsedDate = Date.parse(value);

+

+			if (isNaN((parsedDate))) {

+				throw 'testInstanceEndDate must be a valid date.';

+			}

+		}

+

+		this._testInstanceEndDate = value;

+	}

+

+	get async () {

+		return this._async;

+	}

+

+	set async (value) {

+		this._async = value;

+	}

+

+	get asyncTopic () {

+		return this._asynTopic;

+	}

+

+	set asyncTopic (value) {

+		this._asynTopic = value;

+	}

+

+	get asyncMode () {

+		return this._asyncMode;

+	}

+

+	set asyncMode (value) {

+		this._asyncMode = value;

+	}

+

+	get executorId () {

+		return this._executorId;

+	}

+

+	set executorId (value) {

+		if (!value) {

+			throw 'executorId is required.';

+		}

+

+		if (!utils.isValidObjectId(value)) {

+			throw 'executorId must be a valid ObjectId.';

+		}

+

+		this._executorId = new ObjectId(value);

+	}

+

+	print () {

+		logger.debug(

+			'testInstanceId: ' + this._testInstanceId + '\n' +

+			'testInstanceStartDate: ' + this._testInstanceStartDate + '\n' +

+			'testInstanceExecFreqInSeconds: ' + this._testInstanceExecFreqInSeconds + '\n' +

+			'testInstanceStartDate: ' + this._testInstanceStartDate + '\n' +

+			'async: ' + this._async + '\n' +

+			'asnycTopic: ' + this._asyncTopic + '\n' +

+			'executorId: ' + this._executorId);

+	}

+}

+

+module.exports = TestSchedule;

diff --git a/otf-frontend/server/src/agenda/result-emitter.js b/otf-frontend/server/src/agenda/result-emitter.js
new file mode 100644
index 0000000..cdcf4af
--- /dev/null
+++ b/otf-frontend/server/src/agenda/result-emitter.js
@@ -0,0 +1,32 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const events = require('events');

+const eventEmitter = new events.EventEmitter();

+

+module.exports.emitter = eventEmitter;

+

+// @author: rp978t

+/* @description: This module serves as a common emitter to be used by

+ * the test execution controller and job definitions. The Agenda library

+ * only returns a job object when that object persists in the database.

+ * Therefore, there is no conventional way to return the response from

+ * the service api (inside the job definition) to the controller to be

+ * sent back to the user of the scheduling endpoint (In this case it would

+ * be the UI). Setting up a listener in the controller will allow emitting

+ * an event from the job definition to relay the response. Events are

+ * distinguished by using the job identifier as part of the event name.

+ */

diff --git a/otf-frontend/server/src/app.js b/otf-frontend/server/src/app.js
new file mode 100644
index 0000000..37319bf
--- /dev/null
+++ b/otf-frontend/server/src/app.js
@@ -0,0 +1,101 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const cluster = require('cluster');

+const os = require('os');

+

+// Winston

+const logger = require('./lib/logger');

+

+const jobWorkers = [];

+const expressWorkers = [];

+

+process.env.NODE_CONFIG_DIR = './server/config';

+

+// TODO: Do we need this

+process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0;

+

+const env = process.env['ENV'];

+const validEnvs = ['production', 'development', 'system_test', 'local'];

+

+if (env === undefined) {

+	logger.error('The process environment is not set, so the application will not start.\nPlease set the variable ' +

+		'\'%s\' to one of the following values: %s', 'env', validEnvs);

+	process.exit(1);

+} else if (!validEnvs.includes(env)) {

+	logger.error('%s is not a valid value.\nPlease set the environment variable \'%s\' to one of the following ' +

+		'values: %s', env, 'env', validEnvs);

+	process.exit(1);

+}

+

+// Workers can only be spawned on the master node.

+if (cluster.isMaster) {

+	// Use 8 CPU's on non-local environments, otherwise get the number of CPUs

+	const numWorkers = (env === 'local') ? 1 : 8;

+

+	logger.info('Master node is creating %d workers on the %s environment.', numWorkers, env);

+

+	// Spawns a worker process for every CPU

+	for (let i = 0; i < numWorkers; i++) {

+		addExpressWorker();

+		// Don't add job workers on local environments

+		if (env === 'local') continue;

+		addJobWorker();

+	}

+

+	// Listener for a spawned worker

+	cluster.on('exit', (worker, code, signal) => {

+		logger.info('Worker %d is online.', worker.process.pid);

+

+		if (jobWorkers.indexOf(worker.id) !== -1) {

+			console.log(`job worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...`);

+			removeJobWorker(worker.id);

+			addJobWorker();

+		}

+

+		if (expressWorkers.indexOf(worker.id) !== -1) {

+			console.log(`express worker ${worker.process.pid} exited (signal: ${signal}). Trying to respawn...`);

+			removeExpressWorker(worker.id);

+			addExpressWorker();

+		}

+	});

+} else {

+	if (process.env.express) {

+		logger.info('Created express server process.');

+		require('./feathers/index');

+	}

+

+	if (process.env.job) {

+		logger.info('Created agenda job server process.');

+		require('./agenda/agenda').initializeAgenda();

+	}

+}

+

+function addExpressWorker () {

+	expressWorkers.push(cluster.fork({ express: 1 }).id);

+}

+

+function addJobWorker () {

+	jobWorkers.push(cluster.fork({ job: 1 }).id);

+}

+

+function removeExpressWorker (id) {

+	expressWorkers.splice(expressWorkers.indexOf(id), 1);

+}

+

+function removeJobWorker (id) {

+	jobWorkers.splice(jobWorkers.indexOf(id), 1);

+}

diff --git a/otf-frontend/server/src/feathers/app.hooks.js b/otf-frontend/server/src/feathers/app.hooks.js
new file mode 100644
index 0000000..4cd08ac
--- /dev/null
+++ b/otf-frontend/server/src/feathers/app.hooks.js
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Application hooks that run for every service

+const log = require('./hooks/log');

+const paginateOption = require('./hooks/paginate-option');

+const createdBy = require('./hooks/createdBy');

+const updatedBy = require('./hooks/updatedBy');

+const {iff, disallow, isProvider, skipRemainingHooks} = require('feathers-hooks-common');

+const { ObjectID } = require('mongodb');

+

+module.exports = {

+	before: {

+		all: [paginateOption(), skipRemainingHooks(context => !context.params.provider)],

+		find: [

+			function(context){

+				const {query} = context.params;

+

+				iterate(query, '');

+

+				return context;

+			}

+		],

+		get: [],

+		create: [createdBy()],

+		update: [updatedBy()],

+		patch: [updatedBy()],

+		remove: []

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [log()],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

+

+function iterate(obj, stack) {

+	for (var property in obj) {

+		if (obj.hasOwnProperty(property)) {

+			if (typeof obj[property] == "object") {

+

+				//check for $in

+				if(/\._id$/.test(property) && obj[property]['$in'] && obj[property]['$in'].length && obj[property]['$in'].length > 0){

+					obj[property]['$in'].forEach((elem, val) => {

+						obj[property]['$in'][val] = new ObjectID(elem);

+					})

+				}else{

+					iterate(obj[property], stack + '.' + property);

+				}

+			} else if(/\._id$/.test(property)){

+				obj[property] = new ObjectID(obj[property]);

+			}

+		}

+	}

+}

diff --git a/otf-frontend/server/src/feathers/authentication.js b/otf-frontend/server/src/feathers/authentication.js
new file mode 100644
index 0000000..2a0efc9
--- /dev/null
+++ b/otf-frontend/server/src/feathers/authentication.js
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const authentication = require('@feathersjs/authentication');

+const jwt = require('@feathersjs/authentication-jwt');

+const local = require('@feathersjs/authentication-local');

+const { permissions } = require('./hooks/permissions/permissions');

+// const { hashPassword, protect } = require('@feathersjs/authentication-local').hooks;

+

+module.exports = function (app) {

+	const config = app.get('authentication');

+

+	// Set up authentication with the secret

+	app.configure(authentication(config));

+	app.configure(jwt());

+	app.configure(local());

+

+	// The `authentication` service is used to create a JWT.

+	// The before `create` hook registers strategies that can be used

+	// to create a new valid JWT (e.g. local or oauth2)

+	app.service(config.path).hooks({

+		before: {

+			create: [

+				function(context){

+					 //console.log(context.data)

+					// console.log('authing');

+				},

+				authentication.hooks.authenticate(config.strategies),

+				permissions('authentication')

+			],

+			remove: [

+				authentication.hooks.authenticate('jwt')

+			]

+		},

+		after: {

+			create: [

+				// Send the user profile back with access token

+				async function (context) {

+					if (!context.params.user.enabled) {

+						context.result.accessToken = null;

+					}

+

+					context.result['user'] = context.params.user;

+

+					//Send Back the users rules

+					if(context.params.ability){

+						context.result.user['rules'] = context.params.ability.rules;

+					}

+

+					delete context.result.user.password;

+					return context;

+				}

+			]

+		}

+	});

+};

diff --git a/otf-frontend/server/src/feathers/channels.js b/otf-frontend/server/src/feathers/channels.js
new file mode 100644
index 0000000..a896480
--- /dev/null
+++ b/otf-frontend/server/src/feathers/channels.js
@@ -0,0 +1,78 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = function (app) {

+	if (typeof app.channel !== 'function') {

+		// If no real-time functionality has been configured just return

+		return;

+	}

+

+	app.on('connection', connection => {

+		// On a new real-time connection, add it to the anonymous channel

+		app.channel('anonymous').join(connection);

+		

+	});

+

+	app.on('login', (authResult, { connection }) => {

+		// connection can be undefined if there is no

+		// real-time connection, e.g. when logging in via REST

+		if (connection) {

+			// Obtain the logged in user from the connection

+			// const user = connection.user;

+

+			// The connection is no longer anonymous, remove it

+			app.channel('anonymous').leave(connection);

+

+			// Add it to the authenticated user channel

+			app.channel('authenticated').join(connection);

+

+			// Channels can be named anything and joined on any condition

+

+			// E.g. to send real-time events only to admins use

+			// if(user.isAdmin) { app.channel('admins').join(connection); }

+

+			// If the user has joined e.g. chat rooms

+			// if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(channel));

+

+			// Easily organize users by email and userid for things like messaging

+			// app.channel(`emails/${user.email}`).join(channel);

+			// app.channel(`userIds/$(user.id}`).join(channel);

+		}

+	});

+

+	//eslint-disable-next-line no-unused-vars

+	app.publish((data, hook) => {

+		// Here you can add event publishers to channels set up in `channels.js`

+		// To publish only for a specific event use `app.publish(eventname, () => {})`

+

+		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

+

+		// e.g. to publish all service events to all authenticated users use

+		return app.channel('authenticated');

+	});

+

+	// Here you can also add service specific event publishers

+	// e.g. the publish the `users` service `created` event to the `admins` channel

+	// app.service('users').publish('created', () => app.channel('admins'));

+

+	// With the userid and email organization from above you can easily select involved users

+	// app.service('messages').publish(() => {

+	//   return [

+	//     app.channel(`userIds/${data.createdBy}`),

+	//     app.channel(`emails/${data.recipientEmail}`)

+	//   ];

+	// });

+};

diff --git a/otf-frontend/server/src/feathers/hooks/agendaJobPopulate.js b/otf-frontend/server/src/feathers/hooks/agendaJobPopulate.js
new file mode 100644
index 0000000..51cea24
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/agendaJobPopulate.js
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = function () {

+	return async context => {

+		if (context.result.data && context.result.limit >= 0 ) {

+			for (let i = 0; i < context.result.data.length; i++) {

+				await context.app.services[context.app.get('base-path') + 'test-instances']

+					.get(context.result.data[i].data.testSchedule._testInstanceId, context.params)

+					.then(result => {

+						context.result.data[i].testInstanceName = result.testInstanceName;

+					})

+					.catch(err => {

+						console.log(err);

+					});

+			}

+		} else if(context.result.limit) {

+			await context.app.services[context.app.get('base-path') + 'test-instances']

+				.get(context.result.data.data.testSchedule._testInstanceId, context.params)

+				.then(result => {

+					context.result.data.testInstanceName = result.testInstanceName;

+				})

+				.catch(err => {

+					console.log(err);

+				});

+		}else if (context.result.length) {

+			for (let i = 0; i < context.result.length; i++) {

+				await context.app.services[context.app.get('base-path') + 'test-instances']

+					.get(context.result[i].data.testSchedule._testInstanceId, context.params)

+					.then(result => {

+						context.result[i].testInstanceName = result.testInstanceName;

+					})

+					.catch(err => {

+						console.log(err);

+					});

+			}

+		} else if(context.result.data) {

+			await context.app.services[context.app.get('base-path') + 'test-instances']

+				.get(context.result.data.testSchedule._testInstanceId, context.params)

+				.then(result => {

+					context.result.testInstanceName = result.testInstanceName;

+				})

+				.catch(err => {

+					console.log(err);

+				});

+		}

+		return context;

+	};

+};

diff --git a/otf-frontend/server/src/feathers/hooks/checkLocks.js b/otf-frontend/server/src/feathers/hooks/checkLocks.js
new file mode 100644
index 0000000..b4d527e
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/checkLocks.js
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = function () {

+    return async context => {

+            return await context.app.services[context.app.get('base-path') + 'test-instances']

+                .get(context.data.testInstanceId, context.params)

+                .then( result => {

+                    if(result.disabled === true){

+                        return true;

+                        //throw new Error('Test Instance is locked and cannot be run!');

+                    }else{

+                        return context.app.services[context.app.get('base-path') + 'test-definitions']

+                            .get(result.testDefinitionId, context.params)

+                            .then(results => {

+                                if(results.disabled === true){

+                                    return true;

+                                    

+                                    //throw new Error('Test Definition is locked! The instance can not be run!');

+                                }else{

+                                    return false;

+                                }

+                            })

+                            .catch(err => {

+                                console.log(err);

+                            });

+                    }

+                    

+                })

+                .catch(err => {

+                    console.log(err);

+                });

+                

+            }

+}
\ 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
index 0000000..fb02fa3
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/checkPermissions.js
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = function () {

+    return async context => {

+		let group = {};

+		if(context.data.parentGroupId){

+			//get the groups from the group service

+			//check if the user is an Admin in the parent group 

+			await context.app.services[context.app.get('base-path') + 'groups']

+			.get(context.data.parentGroupId, context.params)

+			.then( result => {	

+				group = result;

+			});

+			

+			if(group.members){

+				for(let i = 0; i < group.members.length; i++){

+					if(group.members[i].userId.toString() === context.params.user._id.toString()){

+						if(!group.members[i].roles.includes("admin")){

+							throw new Error('Can not create child group. You must be an admin of the parent group.');

+						}

+					}

+				}

+			}else{

+				throw new Error('Can not create child group. You must be an admin of the parent group.');

+			}

+		}

+	}

+}
\ 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
index 0000000..0a20378
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/convertToJSON.js
@@ -0,0 +1,142 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const YAML = require('yamljs');

+

+module.exports = function () {

+    function convertTabs(str){

+        return str.replace(/\t/g, '    ');

+    }

+	return async context => {

+		

+		if (context.data.length) {

+			for (var i = 0; i < context.data.length; i++) {

+				if (context.data[i].testData && typeof '' === typeof context.data[i].testData) {

+                    context.data[i].testData = convertTabs(context.data[i].testData);

+					context.data[i].testData = YAML.parse(context.data[i].testData);

+				}

+				if (context.data[i].testConfig) {

+                    context.data[i].testConfig = convertTabs(context.data[i].testConfig);

+					context.data[i].testConfig = YAML.parse(context.data[i].testConfig);

+				}

+				if (context.data[i].testConfigTemplate) {

+                    context.data[i].testConfigTemplate = convertTabs(context.data[i].testConfigTemplate);

+					context.data[i].testConfigTemplate = YAML.parse(context.data[i].testConfigTemplate);

+				}

+				if (context.data[i].vthInputTemplate && typeof '' === typeof context.data[i].vthInputTemplate) {

+					context.data[i].vthInputTemplate = convertTabs(context.data[i].vthInputTemplate);

+					context.data[i].vthInputTemplate = YAML.parse(context.data[i].vthInputTemplate);

+				}

+

+                //Set empty string as empty json

+                if (context.data[i].testData == ''){

+                    context.data[i].testData = {};

+                }

+                if (context.data[i].testConfig == ''){

+                    context.data[i].testConfig = {};

+                }

+				if (context.data[i].testConfigTemplate == '') {

+                    context.data[i].testConfigTemplate = {};

+				}

+				if(context.data[i].bpmnInstances){

+					for(let k in context.data[i].bpmnInstances){

+						let key = context.data[i].bpmnInstances[k];

+						if(key.testHeads){

+							for(let h in key.testHeads){

+								let head = key.testHeads[h];

+								if(head.testHeadId.vthInputTemplate == ''){

+									head.testHeadId.vthInputTemplate = {};

+								}else if(head.testHeadId.vthInputTemplate && typeof '' === typeof head.testHeadId.vthInputTemplate){

+									head.testHeadId.vthInputTemplate = YAML.parse(head.testHeadId.vthInputTemplate);

+								}

+							}

+						}

+						if(key.testDataTemplate == ''){	

+							key.testDataTemplate = {};

+						}else if(typeof '' === typeof key.testDataTemplate){

+							key.testDataTemplate = YAML.parse(key.testDataTemplate);

+						}	

+					}

+				}

+			}

+		} else {

+			if (context.data.testData && typeof '' === typeof context.data.testData) {

+                context.data.testData = convertTabs(context.data.testData);

+				context.data.testData = YAML.parse(context.data.testData);

+			}

+			if (context.data.testConfig) {

+                context.data.testConfig = convertTabs(context.data.testConfig);

+				context.data.testConfig = YAML.parse(context.data.testConfig);

+			}

+			if (context.data.testConfigTemplate) {

+                context.data.testConfigTemplate = convertTabs(context.data.testConfigTemplate);

+				context.data.testConfigTemplate = YAML.parse(context.data.testConfigTemplate);

+			}

+			if (context.data.vthInputTemplate && typeof '' === typeof context.data.vthInputTemplate) {

+                context.data.vthInputTemplate = convertTabs(context.data.vthInputTemplate);

+				context.data.vthInputTemplate = YAML.parse(context.data.vthInputTemplate);

+			}

+			if (context.data.vthInput){

+				for(let k in context.data.vthInput){

+					if(typeof context.data.vthInput[k] === typeof ''){

+						context.data.vthInput[k] = YAML.parse(context.data.vthInput[k]);

+						if(context.data.vthInput[k] === null)

+							context.data.vthInput[k] = {};

+					}

+				}

+			}

+			if(context.data.bpmnInstances){

+				for(let k in context.data.bpmnInstances){

+					let key = context.data.bpmnInstances[k];

+					if(key.testHeads){

+						for(let h in key.testHeads){

+							let head = key.testHeads[h];

+							if(head.testHeadId.vthInputTemplate == ''){

+								head.testHeadId.vthInputTemplate = {};

+							}else if(head.testHeadId.vthInputTemplate && typeof '' === typeof head.testHeadId.vthInputTemplate){

+								head.testHeadId.vthInputTemplate = YAML.parse(head.testHeadId.vthInputTemplate);

+							}

+						}

+					}

+					if(key.testDataTemplate == ''){	

+						key.testDataTemplate = {};

+					}else  if(typeof '' === typeof key.testDataTemplate){

+						key.testDataTemplate = YAML.parse(key.testDataTemplate);

+					}	

+				}

+			}

+			//Set empty string as empty json

+            if (context.data.testData == ''){

+                context.data.testData = {};

+            }

+            if (context.data.testConfig == ''){

+                context.data.testConfig = {};

+            }

+            if (context.data.testConfigTemplate == '') {

+                context.data.testConfigTemplate = {};

+			}

+			if (context.data.vthInputTemplate == '') {

+				context.data.vthInputTemplate = {};

+			}

+		}

+		// for(var j = 0; j < context.data.length; j++){

+		//     for(var i = 0; i < context.data[j].testData.length; i++){

+		// 	    context.data[j].testData[i].testData = YAML.parse(context.data[j].testData[i].testData);

+		//     }

+		// }

+		return context;

+	};

+};

diff --git a/otf-frontend/server/src/feathers/hooks/convertToYAML.js b/otf-frontend/server/src/feathers/hooks/convertToYAML.js
new file mode 100644
index 0000000..6c777d0
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/convertToYAML.js
@@ -0,0 +1,335 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const YAML = require('yamljs');

+

+module.exports = function () {

+	function isEmpty(obj) {

+		for(var key in obj) {

+			if(obj.hasOwnProperty(key))

+				return false;

+		}

+		return true;

+	}

+

+	return async context => {

+		

+		if (context.result.length) {

+			for (let i = 0; i < context.result.length; i++) {

+				if (context.result[i].testData) {

+					if(isEmpty(context.result[i].testData)){

+						context.result[i].testDataJSON = {};

+						context.result[i].testData = '';

+					}else if(typeof context.result[i].testDate != typeof ''){

+						context.result[i].testDataJSON = context.result[i].testData;

+						context.result[i].testData = YAML.stringify(context.result[i].testData);

+					}	

+				}

+				if (context.result[i].testConfig) {

+					if(isEmpty(context.result[i].testConfig)){	

+						context.result[i].testConfig = '';

+					}else{

+						context.result[i].testConfig = YAML.stringify(context.result[i].testConfig);

+					}	

+				}

+				if(context.result[i].vthInputTemplate){

+					if(isEmpty(context.result[i].vthInputTemplate)){

+						context.result[i].vthInputTemplate = '';

+					}else{

+						if(typeof '' !== typeof context.result[i].vthInputTemplate){

+							context.result[i].vthInputTemplateJSON = context.result[i].vthInputTemplate;

+							context.result[i].vthInputTemplate = YAML.stringify(context.result[i].vthInputTemplate);

+						}

+					}

+				}

+				if (context.result[i].vthInput) {

+					context.result[i].vthInputYaml = '';

+					if(isEmpty(context.result[i].vthInput)){

+						context.result[i].vthInput = {};

+					}else{

+						context.result[i].vthInputYaml = {};

+						for(key in context.result[i].vthInput){

+							

+							context.result[i].vthInputYaml[key] = YAML.stringify(context.result[i].vthInput[key]);

+						}

+					}

+					

+				}

+				if (context.result[i].bpmnInstances) {

+					

+					//console.log("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH");

+					// if(context.re)

+					// 	if(isEmpty(context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate)){

+					// 		context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate = '';

+					// 	}else{

+					// 		context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplateJSON = context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate;

+					// 		context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate = YAML.stringify(context.result[i].bpmnInstances[0].testHeads[0].testHeadId.vthInputTemplate);

+					// 	}

+					

+				

+					for(let k = 0; k < context.result[i].bpmnInstances.length; k++){

+						

+						if( context.result[i].bpmnInstances[k].testHeads){

+							for(let h = 0; h <  context.result[i].bpmnInstances[k].testHeads.length; h++){

+								//let head = key.testHeads[h];

+								//console.log("___________________________________________________________");

+								//console.log(context.result[i].bpmnInstances[k].testHeads[h].testHeadId);

+								if(typeof '' != typeof context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){

+									if(isEmpty(context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate)){

+										context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplateJSON = {};

+										context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate = '';

+									}else if(context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){

+										context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplateJSON = context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate;

+										context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate = YAML.stringify(context.result[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate);

+									}

+								}

+							}

+						}

+						if(isEmpty(context.result[i].bpmnInstances[k].testDataTemplate)){	

+							context.result[i].bpmnInstances[k].testDataTemplate = '';

+							context.result[i].bpmnInstances[k].testDataTemplateJSON = {};

+						}else if(typeof context.result[i].bpmnInstances[k].testDataTemplate !== typeof ''){

+							context.result[i].bpmnInstances[k].testDataTemplateJSON = context.result[i].bpmnInstances[k].testDataTemplate;

+							context.result[i].bpmnInstances[k].testDataTemplate = YAML.stringify(context.result[i].bpmnInstances[k].testDataTemplate);

+						}	

+						//context.result[i].bpmnInstances[k] = key;

+					}

+					

+				}

+			}

+		} else if (context.result.total > 1) {

+			for (let i = 0; i < context.result.data.length; i++) {

+				if (context.result.data[i].testData) {

+					if(isEmpty(context.result.data[i].testData)){

+						context.result.data[i].testData = '';

+						context.result.data[i].testDataJSON = {};

+					}else if(typeof context.result.data[i].testDate != typeof ''){

+						context.result.data[i].testDataJSON = context.result.data[i].testData;

+						context.result.data[i].testData = YAML.stringify(context.result.data[i].testData);

+					}

+					

+				}

+				if (context.result.data[i].vthInput) {

+					context.result.data[i].vthInputYaml = '';

+					if(isEmpty(context.result.data[i].vthInput)){

+						context.result.data[i].vthInput = {};

+					}else{

+						context.result.data[i].vthInputYaml = {};

+						for(key in context.result.data[i].vthInput){

+							

+							context.result.data[i].vthInputYaml[key] = YAML.stringify(context.result.data[i].vthInput[key]);

+						}

+					}

+					

+				}

+				if (context.result.data[i].testConfig) {

+					if(isEmpty(context.result.data[i].testConfig)){

+						context.result.data[i].testConfig = '';

+					}else{

+						context.result.data[i].testConfig = YAML.stringify(context.result.data[i].testConfig);

+					}

+				}

+				if (context.result.data[i].testDataTemplate) {

+					if(isEmpty(context.result.data[i].testDataTemplate)){

+						context.result.data[i].testDataTemplate = '';

+						context.result.data[i].testDataTemplateJSON = {};

+					}else{

+						context.result.data[i].testDataTemplateJSON = context.result.data[i].testDataTemplate;

+						context.result.data[i].testDataTemplate = YAML.stringify(context.result.data[i].testDataTemplate);

+					}

+				}

+				if(context.result.data[i].bpmnInstances){

+					for(let k in context.result.data[i].bpmnInstances){

+						let key = context.result.data[i].bpmnInstances[k];

+						if(key.testHeads){

+							for(let h in key.testHeads){

+								let head = key.testHeads[h];

+								

+								if(typeof '' != typeof context.result.data[i].bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){	

+									if(isEmpty(head.testHeadId.vthInputTemplate)){

+										head.testHeadId.vthInputTemplate = '';

+										head.testHeadId.vthInputTemplateJSON = {};

+									}else if(head.testHeadId.vthInputTemplate){

+										head.testHeadId.vthInputTemplateJSON = head.testHeadId.vthInputTemplate;

+										head.testHeadId.vthInputTemplate = YAML.stringify(head.testHeadId.vthInputTemplate);

+									}

+								}

+								key.testHeads[h] = head;

+							}

+						}

+						if(isEmpty(key.testDataTemplate)){	

+							key.testDataTemplate = '';

+							key.testDataTemplateJSON = {};

+						}else if(typeof key.testDataTemplate !== typeof ''){

+							key.testDataTemplateJSON = key.testDataTemplate;

+							key.testDataTemplate = YAML.stringify(key.testDataTemplate);

+						}

+						context.result.data[i].bpmnInstances[k] = key;	

+					}

+				}

+			}

+		} else if (context.result.data) {

+			if (context.result.data.testData) {

+				if(isEmpty(context.result.data.testData)){

+					context.result.data.testData = '';

+					context.result.data.testDataJSON = {};

+				}else if(typeof context.result.data.testDate != typeof ''){

+					context.result.data.testDataJSON = context.result.data.testData;

+					context.result.data.testData = YAML.stringify(context.result.data.testData);

+				}	

+			}

+			if (context.result.data.testConfig) {

+				if(isEmpty(context.result.data.testConfig)){

+					context.result.data.testConfig = '';

+				}else{

+					context.result.data.testConfig = YAML.stringify(context.result.data.testConfig);

+				}	

+			}

+			if (context.result.data.vthInput) {

+				context.result.data.vthInputYaml = '';

+				if(isEmpty(context.result.data.vthInput)){

+					context.result.data.vthInput = {};

+				}else{

+					context.result.data.vthInputYaml = {};

+					for(key in context.result.data.vthInput){

+						context.result.data.vthInputYaml[key] = YAML.stringify(context.result.data.vthInput[key]);

+					}

+				}

+				

+			}

+			if (context.result.data.testDataTemplate) {

+				if(isEmpty(context.result.data.testDataTemplate)){

+					context.result.data.testDataTemplate = '';

+					context.result.data.testDataTemplateJSON = {};

+				}else{

+					context.result.data.testDataTemplateJSON = context.result.data.testDataTemplate;

+					context.result.data.testDataTemplate = YAML.stringify(context.result.data.testDataTemplate);

+				}		

+			}

+			if (context.result.data.bpmnInstances){

+				for(let k in context.result.data.bpmnInstances){

+					let key = context.result.data.bpmnInstances[k];

+					if(key.testHeads){

+						for(let h in key.testHeads){

+							let head = key.testHeads[h];

+							//console.log(head.testHeadId);

+							if(typeof '' != typeof context.result.data.bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){

+								if(isEmpty(head.testHeadId.vthInputTemplate)){

+									head.testHeadId.vthInputTemplate = '';

+									head.testHeadId.vthInputTemplateJSON = {};

+								}else if(head.testHeadId.vthInputTemplate){

+									head.testHeadId.vthInputTemplateJSON = head.testHeadId.vthInputTemplate;

+									head.testHeadId.vthInputTemplate = YAML.stringify(head.testHeadId.vthInputTemplate);

+								}

+							}

+							key.testHeads[h] = head;

+						}

+					}

+					if(isEmpty(key.testDataTemplate)){	

+						key.testDataTemplate = '';

+						key.testDataTemplateJSON = {};

+					}else if(typeof key.testDataTemplate !== typeof ''){

+						key.testDataTemplateJSON = key.testDataTemplate;

+						key.testDataTemplate = YAML.stringify(key.testDataTemplate);

+					}

+					context.result.data.bpmnInstances[k] = key;	

+				}

+			}

+		} else {

+			if (context.result.testData) {

+				

+				if(isEmpty(context.result.testData)){

+					context.result.testData = '';

+					context.result.testDataJSON = {};

+				}else if(typeof context.result.testData != typeof ''){

+					context.result.testDataJSON = context.result.testData;

+					context.result.testData = YAML.stringify(context.result.testData);

+				}else if(typeof context.result.testData == typeof ''){

+					context.result.testDataJSON = YAML.parse(context.result.testData);

+				}

+				

+			}

+			if (context.result.testConfig) {

+				if(isEmpty(context.result.testConfig)){

+					context.result.testConfig = '';

+				}else{

+					context.result.testConfig = YAML.stringify(context.result.testConfig);

+				}

+			}

+			if (context.result.vthInput) {

+				context.result.vthInputYaml = '';

+				if(isEmpty(context.result.vthInput)){

+					context.result.vthInput = {};

+				}else{

+					context.result.vthInputYaml = {};

+					for(key in context.result.vthInput){

+						context.result.vthInputYaml[key] = YAML.stringify(context.result.vthInput[key]);

+					}

+				}

+				

+			}

+			if (context.result.testDataTemplate) {

+				if(isEmpty(context.result.testDataTemplate)){

+					context.result.testDataTemplate = '';

+					context.result.testDataTemplateJSON = {};

+				}else{

+					context.result.testDataTemplateJSON = context.result.testDataTemplate;

+					context.result.testDataTemplate = YAML.stringify(context.result.testDataTemplate);

+				}

+			}

+			if(context.result.vthInputTemplate){

+				if(isEmpty(context.result.vthInputTemplate)){

+					context.result.vthInputTemplate = '';

+					context.result.vthInputTemplateJSON = {};

+				}else{

+					context.result.vthInputTemplateJSON = context.result.vthInputTemplate;

+					context.result.vthInputTemplate = YAML.stringify(context.result.vthInputTemplate);

+				}

+			}

+			if(context.result.bpmnInstances){

+				for(let k in context.result.bpmnInstances){

+					let key = context.result.bpmnInstances[k];

+					if(key.testHeads){

+						for(let h in key.testHeads){

+							let head = key.testHeads[h];

+							//console.log(head.testHeadId);

+							if(typeof '' != typeof context.result.bpmnInstances[k].testHeads[h].testHeadId.vthInputTemplate){

+								if(isEmpty(head.testHeadId.vthInputTemplate)){

+									head.testHeadId.vthInputTemplate = '';

+									head.testHeadId.vthInputTemplateJSON = {};

+								}else if(head.testHeadId.vthInputTemplate){

+									head.testHeadId.vthInputTemplateJSON = head.testHeadId.vthInputTemplate;

+									head.testHeadId.vthInputTemplate = YAML.stringify(head.testHeadId.vthInputTemplate);

+								}

+							}

+							key.testHeads[h] = head;

+						}

+					}

+					if(isEmpty(key.testDataTemplate)){	

+						key.testDataTemplate = '';

+						key.testDataTemplateJSON = {};

+					}else if(typeof key.testDataTemplate !== typeof ''){

+						key.testDataTemplateJSON = key.testDataTemplate;

+						key.testDataTemplate = YAML.stringify(key.testDataTemplate);

+					}	

+					context.result.bpmnInstances[k] = key;

+				}

+			}

+		

+		}

+		return context;

+	};

+};

diff --git a/otf-frontend/server/src/feathers/hooks/convertToYAMLRecursive.js b/otf-frontend/server/src/feathers/hooks/convertToYAMLRecursive.js
new file mode 100644
index 0000000..67b871a
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/convertToYAMLRecursive.js
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const YAML = require('yamljs');

+

+module.exports = function (convertTo) {

+

+	const toConvert = ['testDataTemplate'];

+

+	function convert(p) {

+		for (var key in p) {

+			if (p.hasOwnProperty(key) && (typeof p[key] === 'object' || typeof p[key] === 'array') ) {

+				if (toConvert.indexOf(key) < 0) {

+					convert(p[key])

+				} else {

+					

+					if(convertTo == 'yaml'){

+						p[key] = YAML.stringify(p[key]);

+					}

+					if(convertTo == 'json'){

+						console.log(key)

+						console.log(p[key]);

+						p[key] = convertTabs(p[key]);

+						p[key] = YAML.parse(p[key]);

+						console.log(p[key]);

+					}

+				}

+			}else{

+				if (toConvert.indexOf(key) >= 0) {

+						

+					if(convertTo == 'yaml'){

+						p[key] = YAML.stringify(p[key]);

+					}

+					if(convertTo == 'json'){

+						p[key] = convertTabs(p[key]);

+						p[key] = YAML.parse(p[key]);

+						console.log(p[key])

+					}

+				}

+			}

+		}

+	}

+

+	function convertTabs(str){

+		if(typeof str === 'string'){

+			return str.replace(/\t/g, '    ');

+		}

+    }

+

+	return async context => {

+		if(context.result)

+			convert(context.result);

+		if(context.data)

+			convert(context.data);

+		return context;

+	};

+};

diff --git a/otf-frontend/server/src/feathers/hooks/createdBy.js b/otf-frontend/server/src/feathers/hooks/createdBy.js
new file mode 100644
index 0000000..537db25
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/createdBy.js
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

+module.exports = function () {

+	return async context => {

+        if(!context.data){

+            context.data = {};

+        }

+        if(context.params.user){

+            context.data.createdBy = context.params.user._id;

+        }

+		return context;

+	};

+};

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
index 0000000..718abb5
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/delete-definition.js
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const util = require('../../lib/otf-util');

+const request = require('request');

+module.exports = function (options = {}) { // eslint-disable-line no-unused-vars

+    return async context => {

+        let options = {

+            url: context.app.get('serviceApi').url + 'testStrategy/delete/v1/testDefinitionId/' + context.id,

+            headers: {

+                'Authorization': 'Basic ' + util.base64Encode(context.app.get('serviceApi').aafId + ':' + context.app.get('serviceApi').aafPassword)

+            },

+            rejectUnauthorized: false,

+        }

+        

+        await new Promise((resolve, reject) => {

+            request.delete(options, (err, res, body) => {

+                if(err){

+                    reject(err);

+                }

+                if(res && res.statusCode == 200){

+                    resolve(body);

+                }else{

+                    reject(res);

+                }

+            });

+        }).then(result => {

+            

+        }).catch(err => {

+            console.log(err);

+        });

+    };

+};

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
index 0000000..d3c3ab4
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/delete-version.js
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const util = require('../../lib/otf-util');

+const request = require('request');

+const error = require('@feathersjs/errors');

+module.exports = function (options = {}) { // eslint-disable-line no-unused-vars

+    return async context => {

+

+        //Get test-definition to compare

+        let original = null;

+        await context.app.services[context.app.get('base-path') + 'test-definitions'].get(context.id, context.params).then(result => {

+            original = result;

+        });

+

+        //If there is a bpmn instance that is deployed and is no longer there, delete with service api

+        if (context.data.bpmnInstances && original) {

+            original.bpmnInstances.forEach(async (elem, val) => {

+                let found = false;

+                context.data.bpmnInstances.forEach((e, v) => {

+                    if (elem.version == e.version) {

+                        found = true;

+                    }

+                });

+                if (!found && elem.isDeployed) {

+                    let options = {

+                        url: context.app.get('serviceApi').url + 'testStrategy/delete/v1/deploymentId/' + elem.deploymentId,

+                        headers: {

+                            'Authorization': 'Basic ' + util.base64Encode(context.app.get('serviceApi').aafId + ':' + context.app.get('serviceApi').aafPassword)

+                        },

+                        rejectUnauthorized: false,

+                    }

+                    

+                    await new Promise((resolve, reject) => {

+                        request.delete(options, (err, res, body) => {

+                            if(err){

+                                reject(err);

+                            }

+                            if(res && res.statusCode == 200){

+                                resolve(res);

+                            }else{

+                                reject(res);

+                            }

+                        });

+                    }).then(result => {

+                        if(result.statusCode != 200){

+                            context.error = new error(result.statusCode);

+                            return Promise.reject(context.error);

+                        }

+                    }).catch(err => {

+                        

+                    });

+                }

+            });

+        }

+    };

+};

diff --git a/otf-frontend/server/src/feathers/hooks/filters.js b/otf-frontend/server/src/feathers/hooks/filters.js
new file mode 100644
index 0000000..d81cd3f
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/filters.js
@@ -0,0 +1,225 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Use this hook to manipulate incoming or outgoing data.

+// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html

+const { iff, disallow } = require('feathers-hooks-common');

+const errors = require('@feathersjs/errors');

+const { ObjectID } = require('mongodb');

+//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)}));

+

+module.exports.groupFilter = function (options = null) {

+	return async context => {

+

+		

+		switch(context.method){

+			case 'get':

+				context.params.query._id = new ObjectID(context.id);

+

+				let result = await context.app.services[context.app.get('base-path') + 'groups'].find(context.params);

+				if(result.data && result.data.length > 0){

+					context.result = result.data[0];

+				}else if(result.length > 0){

+					context.result = result[0];

+				}else{

+					context.result = [];

+				}

+				break;

+			case 'find':

+				if(typeof context.params.user._id === 'string'){

+					context.params.user._id = new ObjectID(context.params.user._id);

+				}

+

+				if(!context.params.user.permissions.includes('admin')){

+					context.params.query['members.userId'] = context.params.user._id;

+				}

+

+				let lookup = context.params.query.lookup;

+				delete context.params.query.lookup;

+

+				// If graphLookup is supplied in the query params as true, lookup all parents and children

+				if(lookup == 'up' || lookup == 'both'){

+					context.result = await new Promise((resolve, reject) => {

+						context.app.services[context.app.get('base-path') + 'groups'].Model.aggregate([

+							{

+								$match: context.params.query

+							},

+							{

+								$graphLookup: {

+									from: "groups",

+									startWith: "$parentGroupId",

+									connectFromField: "parentGroupId",

+									connectToField: "_id",

+									as: "parentGroups"

+								}

+							}

+						]).then(res => {

+							resolve(res);

+						}).catch(err => {

+							throw new errors.GeneralError(err);

+						})

+					});

+				}

+				

+				//if user is an admin in one of ther groups, find children groups

+				if(lookup == 'down' || lookup == 'both'){

+					//this will be set if lookup already occured

+					if(context.result){

+						for(let i = 0; i < context.result.length; i++){

+							//only find children if they are admins

+							if(checkGroupForPermission(context.result[i], context.params.user, 'management')){

+								let children = await getChildGroups(context.app.services[context.app.get('base-path') + 'groups'].Model, context.result[i]);

+								context.result[i]['childGroups'] = children;

+							}

+						}

+					}else{

+						context.result = await new Promise(async (resolve, reject) => {

+							context.app.services[context.app.get('base-path') + 'groups'].find(context.params).then(async res => {

+								let results;

+								if(res.total){

+									results = res.data;

+								}else{

+									results = res;

+								}

+								for(let i = 0; i < results.length; i++){

+									if(checkGroupForPermission(results[i], context.params.user, 'management')){

+										results[i]['childGroups'] = await getChildGroups(context.app.services[context.app.get('base-path') + 'groups'].Model, results[i]);

+									}

+								}

+								resolve(results);

+							}).catch(err => {

+								throw new errors.GeneralError(err);

+							})

+						});

+					}

+				}

+

+				break;

+

+			case 'create':

+				break;

+

+			case 'update':

+			case 'patch':

+			case 'remove':

+				break;

+

+			default:

+				break;

+

+

+		}

+

+		return context;

+	};

+};

+

+getChildGroups = async function(model, group){

+	return new Promise(async (resolve, reject) => {

+		let childGroups = [];

+		model.aggregate([

+			{

+				$match: {

+					'parentGroupId': group._id

+				}

+			}

+		]).then(async res => {

+			if(res.length > 0){

+				for(let i = 0; i < res.length; i++){

+					childGroups.push(res[i]);

+					let childern = await getChildGroups(model, res[i]);

+					childern.forEach((elem, val) => {

+						childGroups.push(elem);

+					});

+				}

+			}

+			resolve(childGroups);

+		}).catch(err => {

+			reject(err);

+		})

+

+	})

+}

+

+checkGroupForPermission = function(group, user, permission){

+	let result = false;

+	group.members.forEach((member, val) => {

+		if(member.userId.toString() == user._id.toString()){

+			group.roles.forEach((e,v) => {

+				if(e.permissions.includes(permission)){

+					if(member.roles.includes(e.roleName)){

+						result = true;

+						return;

+					}

+				}

+			});

+			return;

+		}

+	})

+	return result;

+}

+

+module.exports.afterGroups = function(){

+	return async context => {

+

+	}

+}

+

+module.exports.userFilter = function (){

+	return async context => {

+		

+		if(context.params.query){

+			context.params.query._id = context.params.user._id;

+		}

+		if(context.id && context.id != context.params.user._id){

+			throw new errors.Forbidden();

+		}

+		if(context.data){

+			if(context.data._id && context.data._id != context.params.user._id){

+				throw new errors.Forbidden();

+			}

+			//should not be able to edit their groups

+			delete context.data.groups;

+			//should not be able to edit their permissions

+			delete context.data.permissions;

+			

+			delete context.data.createdAt;

+			delete context.data.updatedAt;

+			delete context.data.enabled;

+		}

+	}

+}

+

+module.exports.getGroupFilter = function (options = { key: 'groupId' }) {

+	return async hook => {

+		if(!hook.params.query){

+			hook.params.query = {};

+		}

+		

+		hook.params.query._id = hook.id;

+		delete hook.id;

+		

+		return hook.service.find(hook.params)

+			.then(result => {

+				if (result.data) {

+					hook.result = result.data[0];

+				} else {

+					hook.result = result;

+				}

+				return hook;

+			});

+	};

+};

diff --git a/otf-frontend/server/src/feathers/hooks/log.js b/otf-frontend/server/src/feathers/hooks/log.js
new file mode 100644
index 0000000..253c113
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/log.js
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// A hook that logs service method before, after and error

+// See https://github.com/winstonjs/winston for documentation

+// about the logger.

+const logger = require('../../lib/logger');

+const util = require('util');

+

+// To see more detailed messages, uncomment the following line:

+// logger.level = 'debug';

+

+module.exports = function () {

+	return context => {

+		// This debugs the service call and a stringified version of the hook context

+		// You can customize the message (and logger) to your needs

+		logger.debug(`${context.type} app.service('${context.path}').${context.method}()`);

+

+		if (typeof context.toJSON === 'function' && logger.level === 'debug') {

+			logger.debug('Hook Context', util.inspect(context, { colors: false }));

+		}

+

+		if (context.error) {

+			logger.error(context.error.stack);

+		}

+	};

+};

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
index 0000000..67eb1f0
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/paginate-option.js
@@ -0,0 +1,26 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Gives client option to disable pagination by setting $limit to -1 in params

+module.exports = function () {

+	return async context => {

+		if (context.params.query && context.params.query.$limit == '-1') {

+			context.params.paginate = false;

+			delete context.params.query.$limit;

+		}

+		return context;

+	};

+};

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
index 0000000..8d1d3e5
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/permissions/abilities.js
@@ -0,0 +1,110 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { AbilityBuilder, Ability } = require('@casl/ability');

+const config = require('../../../../config/default.json');

+

+Ability.addAlias('read', ['get', 'find']);

+Ability.addAlias('write', ['create', 'update', 'patch']);

+Ability.addAlias('delete', ['remove']);

+Ability.addAlias('execute', ['create', 'remove']);

+module.exports.defineAbilitiesFor = function (user, groups) {

+    const { rules, can, cannot } = AbilityBuilder.extract();

+    

+    // If user is a site wide admin, they get all access

+    if(user.permissions.includes('admin')){

+        can('execute', 'all');

+        can('management', 'all');

+        can('crud', 'all');

+        can('patch', 'all');

+        return new Ability(rules);

+    }

+

+    //Permissions associated to roles within groups

+	groups.forEach((elem, val) => {

+

+        if(elem.permissions.includes('management')){

+            can('management', 'groups', {_id: elem._id});

+            can('write', 'groups', ['groupDescription', 'members', 'mechanizedIds', 'roles', 'updatedAt', 'updatedBy'], { _id: elem._id });

+            can('write', 'groups', ['ownerId'], { _id: elem._id, ownerId: user._id});

+

+            //remove management from the array of permissions

+            elem.permissions.splice(elem.permissions.indexOf('management'), 1);

+        }

+

+        //Executing Test Instances

+        if(elem.permissions.includes('execute')){

+            can('execute', 'execute');

+            can('execute', 'testInstances', { groupId: elem._id });

+            can('create', 'jobs');

+            can('remove', 'jobs');

+            

+            //remove execute permission from the array of permissions

+            elem.permissions.splice(elem.permissions.indexOf('execute'), 1);

+        }

+

+        //Test Heads can be accessed by members of the group

+        can(elem.permissions, 'testHeads', { groupId: elem._id });

+

+		//Test Definitions can be accessed by members of the group

+        can(elem.permissions, 'testDefinitions', { groupId: elem._id });

+

+		//Test Instances can be accessed by members of the group

+        can(elem.permissions, 'testInstances', { groupId: elem._id });

+

+        //Test Executions can be accessed by members of the group

+        can('read', 'testExecutions', { groupId: elem._id });

+        can('read', 'testExecutions', ["_id", "groupId", "testHeadResults.testHeadId", "testHeadResults.testHeadName", "testHeadResults.testHeadGroupId", "testHeadResults.startTime", "testHeadResults.endTime"], {"testHeadResults.testHeadGroupId": elem._id});

+

+    });

+

+    /*************************************

+    *   TEST HEADS access

+    */

+

+    //-- READ

+    // Users can read all public test heads

+    can('read', 'testHeads', { isPublic: true });

+

+    // Users should never be able to read the credential 

+    cannot('read', 'testHeads', ['authorizationCredential']);

+

+    //-- EXECUTE

+    // Users can execute all public test heads

+    can('execute', 'testHeads', { isPublic: true });

+

+    /*************************************

+    *   USERS access

+    */

+

+    //-- READ

+

+    // Users should be able to view all users' basic information, and can read more information if it is their user object

+    can('read', 'users', ['_id', 'firstName', 'lastName', 'email']);

+    can('read', 'users', ['permissions', 'favorites', 'defaultGroup', 'defaultGroupEnabled'], { _id: user._id });

+

+    //-- WRITE

+

+    // Users should be able to only edit specific fields from their user object

+    can('write', 'users', ['password', 'favorites', 'defaultGroup', 'defaultGroupEnabled', 'updatedBy', 'updatedAt'], { _id: user._id })

+

+    

+

+    //Authentication

+    can(['create', 'remove'], 'authentication');

+    

+    return new Ability(rules);

+}   
\ 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
index 0000000..f6922e5
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/permissions/get-permissions.js
@@ -0,0 +1,93 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = function(userObject, groups, defaultPermissions = []){

+    return getPermissions(userObject, groups, defaultPermissions);

+}

+

+function getPermissions(userObject, groups, defaultPermissions = []){

+    if(!groups || !groups.length){

+        return [];

+    }

+

+    let results = [];

+

+    for(let i = 0; i < groups.length; i++){

+

+        //Get user's roles in group

+        let userInfo = groups[i].members.find((e) => {

+            return e.userId.toString() == userObject._id.toString();

+        });

+

+        //grab permissions

+        //add default that was passed in

+        let perms = JSON.parse(JSON.stringify(defaultPermissions));

+

+        if(userInfo){

+            groups[i].roles.forEach((elem, val) => {

+                if(userInfo.roles.includes(elem.roleName)){

+                    elem.permissions.forEach(elem => {

+                        perms.push(elem);

+                    })

+                }

+            });

+        }

+

+        addGroupPermissions(results, groups[i], perms);

+

+        //Run recusivley for parent and child groups

+        if(groups[i].parentGroups){

+            groups[i].parentGroups.forEach((e, v) => {

+                addGroupPermissions(results, e, ['read'])

+            });

+        }

+        if(groups[i].childGroups){

+            groups[i].childGroups.forEach((e,v) => {

+                addGroupPermissions(results, e, perms);

+            });

+        }

+

+    }

+

+    return results;

+}

+

+function addGroupPermissions(results, group, permissions){

+

+    // Look for group in result to make sure it doesnt alreay exist

+    let groupIndex = null;

+    results.forEach((elem, val) => {

+        if(elem._id.toString() == group._id.toString()){

+            groupIndex = val;

+            return;

+        }

+    })

+

+    //If group doesn't exist add it to the array.

+    if(groupIndex == null){

+        groupIndex = results.push(group) - 1;

+    }

+

+    //add permissions to group 

+    if(results[groupIndex].permissions){

+        permissions = permissions.concat(results[groupIndex].permissions);

+    }

+

+    permissions = new Set(permissions);

+

+    //set permissions

+    results[groupIndex].permissions = Array.from(permissions);

+}
\ 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
index 0000000..3659bde
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/permissions/permissions.js
@@ -0,0 +1,234 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { defineAbilitiesFor } = require('./abilities.js');

+const { toMongoQuery } = require('@casl/mongoose')

+const getPermissions = require('./get-permissions.js');

+const { Forbidden } = require('@feathersjs/errors');

+//const pick = require('lodash.pick');

+var dot = require('dot-object');

+const pick = require('object.pick');

+const { ObjectID } = require('mongodb');

+

+permissions = function (name = null) {

+	return async context => {

+

+		if (!context.params.provider) {

+			return Promise.resolve(context);

+		}

+

+		const action = context.method;

+		const service = name ? context.app.service(context.path) : context.service;

+		const serviceName = name || context.path;

+

+		let groupQueryParams = JSON.parse(JSON.stringify(context.params));

+		groupQueryParams.query = {

+			lookup: 'both'

+		}

+

+		//get groups list

+		let groups = await context.app.services[context.app.get('base-path') + 'groups'].find(groupQueryParams);

+

+		//organize permissions for the groups

+		let groupsPermissions = getPermissions(context.params.user, groups);

+

+		//Define Abilities for the user

+		const ability = defineAbilitiesFor(context.params.user, groupsPermissions);

+

+		//Define the fields that they have access to

+		let allowedFields;

+		if(service.Model){

+			allowedFields = service.Model.accessibleFieldsBy(ability, context.method);

+		}

+

+		const throwUnlessCan = (action, resource, field = null) => {

+			let instance = resource;

+			if(service.Model && typeof resource === 'object'){

+				instance = new service.Model(resource);

+			}else{

+				instance = serviceName;

+			}

+	

+			if (ability.cannot(action, instance, field)) {

+				let message = `You are not allowed to ${action} ${serviceName}`;

+

+				if(field){

+					message += ` on field ${field}`;

+				}

+

+				throw new Forbidden(message);

+			}

+		}

+

+		context.params.ability = ability;

+

+

+		if (context.method === 'create') {

+			throwUnlessCan('create', context.data);

+		}

+

+		if (!context.id) {               

+

+			throwUnlessCan(context.method, serviceName);

+			

+			const query = toMongoQuery(ability, serviceName, action);

+			

+			if (query !== null) {

+				if(context.params.query.$or && query.$or){

+					query.$and = [

+						{$or: Object.assign([], context.params.query.$or)},

+						{$or: Object.assign([], query.$or)}

+					];

+					delete context.params.query.$or;

+					delete query.$or;

+				}

+

+				Object.assign(context.params.query, query);

+

+			} else {

+				context.params.query.$limit = 0;

+			}

+

+			if(context.params.query.$select){

+				//context.params.query.$select = context.params.query.$select.filter(elem => allowedFields.includes(elem));

+				context.params.query.$select = context.params.query.$select.filter(elem => {

+					for(let i = 0; i < allowedFields.length; i++){

+						

+						//if there is dot notation, then it only looks at the parent variable name

+						elem = elem.toString().match(new RegExp(/^(\w+)/))[0];

+

+						if(allowedFields[i] == elem){

+							return true;

+						}

+

+					};

+

+					return false;

+				});

+				

+

+

+			}else{

+				context.params.query.$select = allowedFields;

+			}

+

+			if(context.params.query.$select && context.params.query.$select.length == 0){

+				context.params.query.$select = allowedFields;

+			}

+

+			if(!context.params.query.$select){

+				context.params.query.$select = [];

+			}

+			//groupId is used for permissions conditions and must be selected

+			if(!context.params.query.$select.includes('groupId')){

+				context.params.query.$select.push('groupId');

+			}

+			

+			return context;

+		}

+

+		const params = Object.assign({}, context.params, { provider: null });

+

+		const result = await service.get(context.id, params);

+		throwUnlessCan(action, result);

+

+		if (action === 'get') {

+			context.result = pick(result, allowedFields);

+		}else{

+			if(context.data){

+				Object.keys(context.data).forEach(key => {

+					if(key == "$push"){

+						Object.keys(context.data['$push']).forEach(k => {

+							throwUnlessCan(action, result, k);

+						});

+					}else{

+						throwUnlessCan(action, result, key);

+					}

+				})

+			}

+			//context.data = pick(context.data, allowedFields);

+		}

+

+		return context;

+

+	}

+}

+

+makeObjectIdString = function(obj) {

+	for (var property in obj) {

+		if (obj.hasOwnProperty(property)) {

+			if (typeof obj[property] == "object"){

+				if(ObjectID.isValid(obj[property])) {

+					obj[property] = obj[property].toString()

+				}else{

+					makeObjectIdString(obj[property]);

+				}

+			}

+		}

+	}

+}

+

+myPick = function(elem, allowedFields){

+	//when turning the object into dot notation, we loose the

+	makeObjectIdString(elem);

+

+	let d = dot.dot(elem);

+	let toPick = [];

+	Object.keys(d).forEach((key) => {

+		allowedFields.forEach((f, i) => {

+			let r = '^' + f;

+			if(key.replace(/\.([0-9]+)\./g, '.').match(new RegExp(r))){

+				toPick.push(key);

+			}

+		})

+	});

+	let picked = pick(d, toPick);

+	let obj = dot.object(picked)

+	return obj;

+}

+

+limitFields = function(){

+	return async context => {

+		if(context.result.data && context.result.data.length != undefined){

+			//checkFields(context.params.ability, context.result.data, context.service.Model);

+			context.result.data.forEach((elem, val) => {

+				let instance = new context.service.Model(elem);

+				const allowedFields = instance.accessibleFieldsBy(context.params.ability);

+				//context.result.data[val] = pick(elem, allowedFields);

+				context.result.data[val] = myPick(elem, allowedFields);

+			});

+		}else if(context.result && context.result.length != undefined){

+			context.result.forEach((elem, val) => {

+				let instance = new context.service.Model(elem);

+				const allowedFields = instance.accessibleFieldsBy(context.params.ability);

+				//context.result[val] = pick(elem, allowedFields);

+				context.result[val] = myPick(elem, allowedFields);

+			});

+		}else if(context.result){

+			//checkFields(context.params.ability, context.result, context.service.Model); 

+			let instance = new context.service.Model(context.result);

+			let allowedFields = instance.accessibleFieldsBy(context.params.ability);

+			//context.result = pick(context.result, allowedFields);

+			context.result = myPick(context.result, allowedFields);

+		}

+	}

+}

+

+

+module.exports = {

+	permissions: permissions,

+	limitFields: limitFields

+}

diff --git a/otf-frontend/server/src/feathers/hooks/testDefinitionIsDeployed.js b/otf-frontend/server/src/feathers/hooks/testDefinitionIsDeployed.js
new file mode 100644
index 0000000..1ca50a1
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/testDefinitionIsDeployed.js
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = function (context) {

+	return async context => {

+        function hasDeployedBpmn(){

+            return context.app.services[context.app.get('base-path') + 'test-definitions']

+                    .get(context.data.testDefinitionId, context.params)

+                    .then(result => {

+                        

+                        if(!result.bpmnInstances){

+                            return false;

+                        }

+                       

+                        for(let i = 0; i < result.bpmnInstances.length; i++){

+                            if(result.bpmnInstances[i].isDeployed){

+                                return true;

+                            }

+                        }

+                        return false;

+                    })

+                    .catch(err => {

+                        console.log(err);

+                    });

+           

+        }

+        

+        if(context.data.processDefinitionId === '' && !context.data.useLatestTestDefinition){

+            return false;

+        }

+        if(!hasDeployedBpmn()){

+            return false;

+        }

+        if(hasDeployedBpmn() && (context.data.useLatestTestDefinition || context.data.processDefinitionId !== '') ){

+            return true;

+        }

+    }

+}
\ 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
index 0000000..9e55af0
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/throw.js
@@ -0,0 +1,21 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = function(error = 'Error') {

+    return async context => {

+        throw error;

+    }

+}
\ 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
index 0000000..cd1cdae
--- /dev/null
+++ b/otf-frontend/server/src/feathers/hooks/updatedBy.js
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

+module.exports = function () {

+	return async context => {

+        if(!context.data){

+            context.data = {};

+        }

+        if(context.params.user){

+            context.data.updatedBy = context.params.user._id;

+        }

+		return context;

+	};

+};

diff --git a/otf-frontend/server/src/feathers/index.js b/otf-frontend/server/src/feathers/index.js
new file mode 100644
index 0000000..ad37c1b
--- /dev/null
+++ b/otf-frontend/server/src/feathers/index.js
@@ -0,0 +1,166 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Node.js modules

+const path = require('path');

+const https = require('https');

+const http = require('http');

+const fs = require('fs');

+

+// Express.js modules

+const express = require('@feathersjs/express');

+const compress = require('compression');

+const helmet = require('helmet');

+const cors = require('cors');

+const favicon = require('serve-favicon');

+

+// Feathers.js modules

+const feathers = require('@feathersjs/feathers');

+const configuration = require('@feathersjs/configuration');

+const socketio = require('@feathersjs/socketio'); //require('@feathersjs/socketio-client'); 

+const io = require('socket.io'); //socket.io-client

+const socket = io();

+

+const services = require('./services');

+const appHooks = require('./app.hooks');

+const channels = require('./channels');

+const authentication = require('./authentication');

+

+// Mongoose

+const mongoose = require('../lib/mongoose');

+const _mongoose = require('mongoose');

+

+// Mongoose Plugins

+const { accessibleRecordsPlugin, accessibleFieldsPlugin } = require('@casl/mongoose');

+_mongoose.plugin(accessibleFieldsPlugin);

+_mongoose.plugin(accessibleRecordsPlugin);

+

+// Winston

+const logger = require('../lib/logger');

+

+// Redis

+const redis = require('redis');

+

+// Create a Express/Feathers application

+const app = express(feathers());

+

+// Load app configuration

+app.configure(configuration());

+

+// Enable security, CORS, compression, favicon and body parsing

+app.use(helmet());

+app.use(cors());

+app.use(compress());

+app.use(express.json());

+app.use(express.urlencoded({ extended: true }));

+

+// Set up Plugins and providers

+app.configure(express.rest());

+app.configure(socketio(function (io) {

+	io.on('connection', (socket) => {

+		console.log('someone has connected')

+		io.emit('message', "HI from nodejs");

+	});

+	// Registering Socket.io middleware

+	io.use(function (socket, next) {

+		// Exposing a request property to services and hooks

+		socket.feathers.referrer = socket.request.referrer;

+		next();

+	});

+}))

+//app.configure(socketio());

+

+// const subscribe = redis.createClient(6379, 'localhost');

+// subscribe.subscribe('otf.execution.queue');

+

+// subscribe.on('connect', function () {

+// 	console.log("Connected to reids server")

+// })

+

+// subscribe.on('message', function (channel, message) {

+// 	console.log('Channel: ' + channel + ', Message: ' + message);

+// 	//client.sent(message);

+// });

+

+// io.on('connection', (socket) => {

+// 	console.log('user connected');

+

+// 	socket.on('message', (message) => {

+// 		console.log("Message Received: " + message);

+// 		io.emit('message', {type: 'new-message', text: message})

+// 	});

+// });

+

+// Configure Mongoose driver before setting up services that use Mongoose

+app.configure(mongoose);

+

+// Set up database dependent components once the connection is ready to prevent unexpected results

+_mongoose.connection.on('open', (ref) => {

+	app.configure(authentication);

+

+	// Set up our services (see `services/index.js`)

+	app.configure(services);

+	// Set up event channels (see channels.js)

+	app.configure(channels);

+

+	const userInterfacePath = path.join(__dirname, '..', '..', '..', 'client', 'dist');

+

+	app.use('/', express.static(userInterfacePath));

+

+	app.all('/*', function (req, res) {

+		res.sendFile(path.join(userInterfacePath, 'index.html'), function (err) {

+			if (err) {

+				res.status(500).send('Internal Server Error - This incident has been reported.');

+				logger.error(JSON.stringify(err));

+			}

+		});

+	});

+

+	// Configure a middleware for 404s and the error handler

+	app.use(express.notFound());

+	app.use(express.errorHandler({ logger }));

+

+	app.hooks(appHooks);

+

+	const port = app.get('port');

+	const useSSL = app.get('ssl');

+	var server = null;

+

+	if(useSSL){

+		// set up server with ssl (https)

+		const certDirPath = path.join(__dirname, '..', '..', '..', 'server', 'config', 'cert');

+

+		server = https.createServer({

+			key: fs.readFileSync(path.normalize(certDirPath + path.sep + 'privateKey.pem')),

+			cert: fs.readFileSync(path.normalize(certDirPath + path.sep + 'otf.pem'))

+		}, app).listen(port);

+	}else{

+		// set up server without ssl (http)

+		server = http.createServer(app).listen(port);

+	}

+

+	app.setup(server);

+

+	process.on('unhandledRejection', (reason, p) =>

+		logger.error('Unhandled Rejection at: Promise ', p, reason)

+	);

+

+	server.on('listening', () =>

+		logger.info('Feathers application started on http://%s:%d', app.get('host'), port)

+	);

+});

+

+module.exports = app;

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
index 0000000..a41d24c
--- /dev/null
+++ b/otf-frontend/server/src/feathers/models/file.model.js
@@ -0,0 +1,29 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const mongooseGridFS = require('mongoose-gridfs');

+

+module.exports = function (app) {

+	const mongoose = app.get('mongooseClient');

+

+	const gridfs = mongooseGridFS({

+		collection: 'fs',

+		model: 'File',

+		mongooseConnection: mongoose.connection

+	});

+

+	return gridfs.model;

+};

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
index 0000000..d15894e
--- /dev/null
+++ b/otf-frontend/server/src/feathers/models/groups.model.js
@@ -0,0 +1,39 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = function (app) {

+	const mongooseClient = app.get('mongooseClient');

+	const { Schema } = mongooseClient;

+	const groups = new Schema({

+		groupName: { type: String, required: true },

+		groupDescription: { type: String },

+		parentGroupId: { type: Schema.Types.ObjectId, ref: 'groups' },

+		members: [ new Schema({

+			userId: { type: Schema.Types.ObjectId, ref: 'users' },

+			roles: { type: Array, default: ['user'] }

+		},  { _id: false })],

+		roles: [new Schema({

+			roleName: { type: String },

+			permissions: { type: Array, default: ['read'] }

+		}, {_id: false})],

+		ownerId: { type: Schema.Types.ObjectId, ref: 'users', required: true },

+		mechanizedIds: [String]

+	}, {

+		timestamps: true

+	});

+

+	return mongooseClient.model('groups', groups);

+};

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
index 0000000..35ad8b3
--- /dev/null
+++ b/otf-frontend/server/src/feathers/models/jobs.model.js
@@ -0,0 +1,45 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = function (app) {

+	const mongooseClient = app.get('mongooseClient');

+	const { Schema } = mongooseClient;

+

+	const jobs = new Schema({

+		name: { type: String },

+		data: { type: new Schema({

+			testSchedule: {

+				testInstanceId: { type: Schema.Types.ObjectId },

+				testInstanceStartDate: { type: String },

+				async: { type: Boolean },

+				asyncTopic: { type: String },

+				testInstanceExecFreqInSeconds: { type: Number },

+				testInstanceEndDate: { type: String },

+				executorId: { type: Schema.Types.ObjectId }

+			},

+			authorizationHeader: { type: String }

+		}) },

+		type: { type: String },

+		nextRunAt: { type: String },

+		lastModifiedBy: { type: String },

+		lockedAt: { type: String },

+		lastRunAt: { type: String }

+	}, {

+		timestamps: true

+	});

+

+	return mongooseClient.model('jobs', jobs, 'agenda');

+};

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
index 0000000..9e71a0f
--- /dev/null
+++ b/otf-frontend/server/src/feathers/models/test-definitions.model.js
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

+module.exports = function (app) {

+	const mongooseClient = app.get('mongooseClient');

+	const { Schema } = mongooseClient;

+	const testDefinitions = new Schema({

+		testName: { type: String, required: true },

+		testDescription: { type: String, required: true },

+		processDefinitionKey: { type: String, unique: true },

+		creatorId: { type: Schema.Types.ObjectId, ref: 'users', required: true },

+		groupId: { type: Schema.Types.ObjectId, ref: 'groups', required: true },

+		bpmnInstances: [new Schema({

+			processDefinitionId: { type: String },

+			deploymentId: { type: String },

+			version: { type: String, required: true },

+			bpmnFileId: { type: Schema.Types.ObjectId, ref: 'files', required: true },

+			resourceFileId: {type: Schema.Types.ObjectId, ref: 'files' },

+			isDeployed: { type: Boolean, required: true, default: false },

+			testHeads: [new Schema({

+				testHeadId: { type: Schema.Types.ObjectId, ref: 'testHeads' },

+				bpmnVthTaskId: { type: String },

+				label: { type: String, default: '' }

+			}, { _id: false })],

+			pflos: [new Schema({

+				bpmnPfloTaskId: { type: String },

+				label: { type: String, default: '' }

+			}, { _id: false })],

+			testDataTemplate: { type: Object, default: {} }, 

+			updatedBy: { type: Schema.Types.ObjectId, ref: 'users'},

+			createdBy: { type: Schema.Types.ObjectId, ref: 'users'}

+		}, { _id: false, timestamps: true })],

+		disabled: {type: Boolean, default: false},

+		updatedBy: { type: Schema.Types.ObjectId, ref: 'users'},

+		createdBy: { type: Schema.Types.ObjectId, ref: 'users'}

+	}, {

+	timestamps: true,

+	minimize: false

+	});

+

+	

+	

+	return mongooseClient.model('testDefinitions', testDefinitions, 'testDefinitions');

+};

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
index 0000000..2f152ac
--- /dev/null
+++ b/otf-frontend/server/src/feathers/models/test-executions.model.js
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

+module.exports = function (app) {

+	const mongooseClient = app.get('mongooseClient');

+

+	const { Schema } = mongooseClient;

+	const testExecutions = new Schema({

+		processInstanceId: { type: String, required: true },

+		businessKey: { type: String },

+		testResult: { type: String },

+		testDetails: { type: Object },

+		startTime: { type: Date },

+		endTime: { type: Date },

+		async: { type: Boolean },

+		asyncTopic: { type: String },

+		groupId: { type: Schema.Types.ObjectId, ref: 'groups', required: true },

+		executorId: { type: Schema.Types.ObjectId, ref: 'users', required: true },

+		testResultMessage: { type: String },

+		testHeadResults: [

+			new Schema({

+				testHeadName: { type: String },

+				testHeadId: { type: Schema.Types.ObjectId, ref: 'testHeads' },

+				testHeadGroupId: { type: Schema.Types.ObjectId, ref: 'groups' }

+			}, {_id: false})

+		],

+		testInstanceResults: [{}],

+		historicEmail: { type: String },

+		historicTestInstance: { type: Object },

+		historicTestDefinition: { type: Object }

+

+	}, {

+			timestamps: false

+		});

+	

+	testExecutions.index({startTime: 1, endTime: 1});

+

+	return mongooseClient.model('testExecutions', testExecutions, 'testExecutions');

+};

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
index 0000000..b1bb52d
--- /dev/null
+++ b/otf-frontend/server/src/feathers/models/test-heads.model.js
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

+module.exports = function (app) {

+	const mongooseClient = app.get('mongooseClient');

+	const { Schema } = mongooseClient;

+	const testHeads = new Schema({

+		testHeadName: { type: String, required: true, unique: true },

+		testHeadDescription: { type: String, required: true },

+		testHeadType: { type: String },

+		vthInputTemplate: { type: Object, default: {} },

+		//vthOutputTemplate: { type: Object, default: {} },

+		vendor: String,

+		port: { type: String },

+		hostname: { type: String },

+		resourcePath: { type: String },

+		creatorId: { type: Schema.Types.ObjectId, ref: 'users' },

+		groupId: { type: Schema.Types.ObjectId, ref: 'groups', required: true },

+		authorizationType: { type: String },

+		authorizationCredential: { type: String },

+		authorizationEnabled: { type: Boolean, default: false },

+		isPublic: { type: Boolean }

+	}, {

+		timestamps: true

+	});

+

+	return mongooseClient.model('testHeads', testHeads, 'testHeads');

+};

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
index 0000000..1cb5f0e
--- /dev/null
+++ b/otf-frontend/server/src/feathers/models/test-instances.model.js
@@ -0,0 +1,71 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

+module.exports = function (app) {

+	const mongooseClient = app.get('mongooseClient');

+	const { Schema } = mongooseClient;

+	const uniqueTIByTD = new Schema({

+		testInstanceName: { type: String, required: true },

+		testDefinitionId: { type: Schema.Types.ObjectId, ref: 'testDefinitions', required: true }

+	});

+	uniqueTIByTD.index({

+		testInstanceName: 1,

+		testDefinitionId: 1,

+	}, {

+			unique: true,

+		});

+

+	const testInstances = new Schema({

+

+		testInstanceDescription: { type: String },

+		testInstanceName: { type: String, required: true },

+		testDefinitionId: { type: Schema.Types.ObjectId, ref: 'testDefinitions', required: true },

+		useLatestTestDefinition: { type: Boolean, required: true },

+		processDefinitionId: { type: String, default: '' },

+		testData: { type: Object, default: {} },

+		internalTestData: { type: Object, default: {} },

+		simulationMode: { type: Boolean, default: false },

+		simulationVthInput: { type: Object, default: {} },

+		vthInput: { type: Object, default: {} },

+		pfloInput: { type: Object, default: {} },

+		disabled: { type: Boolean, default: false },

+		maxExecutionTimeInMillis: { type: Number, default: 0 },

+

+		groupId: { type: Schema.Types.ObjectId, ref: 'groups', required: true },

+		updatedBy: { type: Schema.Types.ObjectId, ref: 'users' },

+		createdBy: { type: Schema.Types.ObjectId, ref: 'users' }

+	}, {

+			timestamps: true,

+			minimize: false

+		});

+	testInstances.index({

+		testInstanceName: 1,

+		testDefinitionId: 1,

+	}, {

+			unique: true,

+		});

+

+	testInstances.post('save', function (error, doc, next) {

+		if (error.name === 'MongoError' && error.code === 11000) {

+			next(new Error('Test Instance name must be unique per Test Definition'));

+		} else {

+			next();

+		}

+	});

+

+	return mongooseClient.model('testInstances', testInstances, 'testInstances');

+};
\ 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
index 0000000..aba50c1
--- /dev/null
+++ b/otf-frontend/server/src/feathers/models/users.model.js
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

+module.exports = function (app) {

+	const mongooseClient = app.get('mongooseClient');

+	

+	const { Schema } = mongooseClient;

+	const users = new Schema({

+		firstName: { type: String, required: true },

+		lastName: { type: String, required: true },

+		email: { type: String, required: true, unique: true },

+		permissions: { type: Array, default: ['user'] },

+		enabled: { type: Boolean, required: true, default: false },

+		isVerified: { type: Boolean },

+		verifyToken: { type: String },

+		verifyExpires: { type: Date },

+		verifyChanges: { type: Object },

+		resetToken: { type: String },

+		resetExpires: { type: Date },

+		defaultGroup: { type: Schema.Types.ObjectId, ref: 'groups' },

+		defaultGroupEnabled: { type: Boolean, default: false },

+		password: { type: String, required: true },

+		favorites: new Schema({

+			testDefinitions: [{type: Schema.Types.ObjectId, ref: 'testDefinitions'}]

+		}, { _id: false})

+	}, {

+		timestamps: true

+	});

+

+	return mongooseClient.model('users', users);

+

+};

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
index 0000000..6cfb072
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/auth-management/auth-management.hooks.js
@@ -0,0 +1,70 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const commonHooks = require('feathers-hooks-common');

+

+module.exports = {

+	before: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [

+			commonHooks.iff(

+			commonHooks.isProvider('external'),

+			commonHooks.preventChanges(

+				'email',

+				'isVerified',

+				'verifyToken',

+				'verifyShortToken',

+				'verifyExpires',

+				'verifyChanges',

+				'resetToken',

+				'resetShortToken',

+				'resetExpires'

+			))],

+		patch: [],

+		remove: []

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [

+			function(context){

+				if(context.result['isVerified']){

+					context.result = {};

+					return context;

+				}

+				return context;

+			}

+		],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..cb3bafd
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/auth-management/auth-management.service.js
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `authmanagement` service on path `/authmanagement`

+// this service is used for user verification and management

+const authManagement = require('feathers-authentication-management');

+const hooks = require('./auth-management.hooks.js');

+const notifier = require('./notifier.js');

+

+module.exports = function (app) {

+

+	// Initialize our service with any options it requires

+	app.configure(authManagement({

+		path: app.get('base-path') + 'authManagement',

+		notifier: notifier(app).notifier,

+		service: app.get('base-path') + 'users'

+	}));

+

+	// Get our initialized service so that we can register hooks and filters

+	const service = app.service(app.get('base-path') + 'authManagement');

+

+	service.hooks(hooks);

+};

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
index 0000000..75bbfa6
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/auth-management/notifier.js
@@ -0,0 +1,120 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+var env = require('config').env;

+

+module.exports = function(app) {

+

+	function getLink(type, hash) {

+		const url = app.get('otf').url + "account/" + type + '?token=' + hash;

+		return url

+	}

+

+	function sendEmail(email) {

+		var environment = env.toUpperCase();

+		email.subject = "Open Test Framework (" + environment + ") - " + email.subject;

+		return app.service(app.get('base-path') + 'mailer').create(email).then(function (result) {

+			console.log('Sent email', result)

+		}).catch(err => {

+			console.log('Error sending email: ', email, err)

+		})

+	}

+

+	return {

+		notifier: function(type, user, notifierOptions) {

+			let tokenLink;

+			let email;

+			let sender = app.get('otf').email;

+			switch (type) {

+				case 'resendVerifySignup': //sending the user the verification email

+					tokenLink = getLink('verify', user.verifyToken)

+					email = {

+						from: sender,

+						to: user['email'],

+						subject: 'Verify Signup',

+						html: 'Please verify your email address by clicking the link below.' + '</br>' + tokenLink

+

+					}

+					return sendEmail(email)

+					break

+

+				case 'verifySignup': // confirming verification

+					let adminLink = app.get('otf').url + 'user-management?filter=' + user['email'];

+

+					email = {

+						from: sender,

+						to: user['email'],

+						subject: 'Signup Confirmed',

+						html: 'Thanks for verifying your email!' + '</br>' + 'You will be notified when an admin enables your account.'

+					}

+

+					let adminEmail = {

+						from: sender,

+						to: sender,

+						subject: 'Approve Verified User',

+						html:   'User has verified their email.' + '</br>' +

+								'Details: ' + '</br>' +

+								'   Email: ' + user['email'] + '</br>' +

+								'   First Name: ' + user['firstName'] + '</br>' +

+								'   Last Name: ' + user['lastName'] + '</br>' +

+								'</br>' +

+								'Enable their account by visiting ' + '</br>' + adminLink

+					}

+					sendEmail(adminEmail);

+					return sendEmail(email);

+					break

+

+				case 'sendApprovalNotification':

+					email = {

+						from: sender,

+						to: user['email'],

+						subject: 'Approved',

+						html:   'Your account has been approved for access.' + '</br>' +

+								'You can now log into the OTF website: ' + app.get('otf').url

+

+					}

+					return sendEmail(email);

+					break

+

+				case 'sendResetPwd':

+					tokenLink = getLink('reset', user.resetToken)

+					email = {}

+					return sendEmail(email)

+					break

+

+				case 'resetPwd':

+					tokenLink = getLink('reset', user.resetToken)

+					email = {}

+					return sendEmail(email)

+					break

+

+				case 'passwordChange':

+					email = {}

+					return sendEmail(email)

+					break

+

+				case 'identityChange':

+					tokenLink = getLink('verifyChanges', user.verifyToken)

+					email = {}

+					return sendEmail(email)

+					break

+

+				default:

+					break

+			}

+		}

+	}

+}

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
index 0000000..fcc11e8
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.class.js
@@ -0,0 +1,196 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const axios = require('axios');

+const pickleRick = require('pickle-rick');

+const Response = require('http-response-object');

+const logger = require('../../../lib/logger');

+const util = require('../../../lib/otf-util');

+const Readable = require('stream').Readable;

+const request = require('request');

+

+class Service {

+	constructor(options) {

+		this.options = options || {};

+	}

+

+	async find(params) {

+		return [];

+	}

+

+	async get(id, params) {

+		return {

+			id, text: `A new message with ID: ${id}!`

+		};

+	}

+

+	async create(data, params) {

+		if (Array.isArray(data)) {

+			return Promise.all(data.map(current => this.create(current, params)));

+		}

+

+		data.deploying = true;

+

+		//let formData = new FormData();

+		let formData = {};

+		//prepare multipart form data

+		//formData.append('testDefinitionDeployerId', JSON.stringify(params.user._id));

+		formData['testDefinitionDeployerId'] = JSON.stringify(params.user._id);

+		if (data.testDefinition._id) {

+			//formData.append('testDefinitionId', JSON.stringify(data.testDefinition._id));

+			formData['testDefinitionId'] = JSON.stringify(data.testDefinition._id);

+		}

+

+		//If version was supplied change current version

+		if(data.version != null && data.version != undefined){

+			data.testDefinition.currentVersion = data.testDefinition.bpmnInstances.findIndex(e => e.version == data.version);

+		}

+

+		//get bpmnfile

+		await this.options.app.services[this.options.app.get('base-path') + 'file-transfer'].get(data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].bpmnFileId)

+			.then(result => {

+				// let b = new Buffer(result);

+				// console.log(b.toString())

+				let s = new Readable();

+

+				s.push(result);

+				s.push(null);

+				formData['bpmn'] = s.read();

+				// s.pipe(formData['bpmn']);

+				

+			}).catch(err => {

+				console.log(err);

+			});

+

+

+		//get resource zip file

+		if (data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].resourceFileId) {

+			await this.options.app.services[this.options.app.get('base-path') + 'file-transfer'].get(data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].resourceFileId)

+				.then(result => {

+					//let b = new Buffer(result);

+					let s = new Readable();

+

+					s.push(result);

+					s.push(null);

+					

+					formData['resources'] = s.read();

+					//formData.append('resource', s);

+

+				}).catch(err => {

+					console.log(err);

+				});

+		}

+

+		//prepare request

+		let options = {

+			url: this.options.app.get('serviceApi').url + 'testStrategy/deploy/v1',

+			headers: {

+				'Authorization': 'Basic ' + util.base64Encode(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword),

+				'Content-Type': "multipart/form-data"

+			},

+			rejectUnauthorized: false,

+			formData: formData

+		}

+		let deployed = false;

+		let deployedDefinition;

+		let response;

+		await new Promise((resolve, reject) => {

+			request.post(options, (err, res, body) => {

+				response = res || err;

+				if(err){

+					reject(err);

+				}

+				if(res && res.statusCode == 200){

+					deployed = true;

+					resolve(body);

+				}else{

+					reject(res);

+				}

+			});

+		}).then(

+			result => {

+				if(result){

+					deployedDefinition = JSON.parse(result);

+				}

+			}

+		).catch(

+			err => {

+				console.log(err.body);

+			}

+		);

+		if (!deployed) {

+			pickleRick();

+			return new Response(500, {}, { errors: { deployment: 'The bpmn file failed to deploy on the server.' } });

+		}

+

+		// Since test head objects are sent, we only store the test head id. this for loop adds those to the object to save

+		// for (let i = 0; i < data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].testHeads.length; i++) {

+		// 	data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].testHeads[i].testHeadId = data.testDefinition.bpmnInstances[data.testDefinition.currentVersion].testHeads[i].testHead._id;

+		// }

+

+		// let td = await this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].create(data.testDefinition, params)

+		//     .then(result => {

+		//         return result['data'];

+		//     })

+		//     .catch(err => {

+		//         console.log(err);

+		//     }

+		// Set as deployed

+		delete params.query;

+

+		//check to see if the process definition Key was set

+		// if (!data.testDefinition.processDefinitionKey) {

+		// 	data.testDefinition.processDefinitionKey = validated.body.processDefinitionKey;

+		// }

+		let td = await this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].patch(data.testDefinition._id, {

+			$set:{

+				['bpmnInstances.' + data.testDefinition.currentVersion + '.isDeployed']: true,

+				['bpmnInstances.' + data.testDefinition.currentVersion + '.processDefinitionId']: deployedDefinition['processDefinitionId'],

+				['bpmnInstances.' + data.testDefinition.currentVersion + '.deploymentId']: deployedDefinition['deploymentId']

+			}

+		}, params)

+			.then(result => {

+				return result;

+			})

+			.catch(err => {

+				logger.error(err);

+			});

+

+		return new Response(200, {}, {

+			//bpmnVthTaskIds: validated.body.bpmnVthTaskIds,

+			//errors: validated.body.errors,

+			testDefinition: td

+		});

+	}

+

+	async update(id, data, params) {

+		return data;

+	}

+

+	async patch(id, data, params) {

+		return data;

+	}

+

+	async remove(id, params) {

+		return { id };

+	}

+}

+

+module.exports = function (options) {

+	return new Service(options);

+};

+

+module.exports.Service = Service;

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
index 0000000..614d368
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.hooks.js
@@ -0,0 +1,48 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { permissions } = require('../../hooks/permissions/permissions');

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt')],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..ae38535
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/bpmn-upload/bpmn-upload.service.js
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const createService = require('./bpmn-upload.class.js');

+const hooks = require('./bpmn-upload.hooks');

+

+module.exports = function (app) {

+	const paginate = app.get('paginate');

+

+	const options = {

+		paginate,

+		app

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'bpmn-upload', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'bpmn-upload');

+

+	service.hooks(hooks);

+};

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
index 0000000..8b969bc
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.class.js
@@ -0,0 +1,297 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const axios = require('axios');

+const Response = require('http-response-object');

+const logger = require('../../../lib/logger');

+const util = require('../../../lib/otf-util');

+const beautify = require('json-beautify');

+const Bpmn = require('./bpmn.class');

+const Readable = require('stream').Readable;

+const request = require('request');

+

+class Service {

+	constructor (options) {

+		this.options = options || {};

+	}

+

+	async find (params) {

+		return [];

+	}

+

+	// Check process definition key to see if unique

+	async get (id, params) {

+		let errors = {};

+		// Get List of Definition keys from Camunda

+		let options = {

+			url: this.options.app.get('camundaApi').url + 'otf/tcu/testDefinition/v1/processDefinitionKey/' + id,

+			headers: {

+				'Authorization': 'Basic ' + new Buffer(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword).toString('base64')

+			},

+			rejectUnauthorized: false

+		};

+

+		return await new Promise(async (resolve, reject) => {

+			request.get(options, (err, response, body) => {

+				if(err){

+					reject(err);

+				}

+				resolve(response);

+			});

+		})

+		.then(

+			result => {

+				if (result.statusCode == 200) {

+					//check to make sure they have access

+					params.query.$limit = '-1';

+					params.query.processDefinitionKey = id;

+					return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(params).then(

+						res => {

+							if(res.length > 0){

+								return new Response(200, {}, res);

+							}else{

+								let resp = new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}});

+								return resp;

+							}

+						}

+					);

+				}else{

+					return new Response(200, {});

+				}

+			}

+		).catch(err => {

+			return new Response(400, {});

+		});

+

+		// return await axios.get(

+		// 	this.options.app.get('camundaApi').url + 'otf/tcu/testDefinition/v1/processDefinitionKey/' + id,

+		// 	{

+		// 		headers: {

+		// 			Authorization: 'Basic ' + new Buffer(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword).toString('base64')

+		// 		}

+		// 	})

+		// 	.then(result => {

+		// 		console.log(result);

+		// 		if (result.status === 200) {

+		// 			//check to make sure they have access

+		// 			params.query.$limit = '-1';

+		// 			params.query.processDefinitionKey = id;

+		// 			return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(params).then(

+		// 				res => {

+		// 					console.log('res 1');

+		// 					console.log(res);

+		// 					if(res.length > 0){

+		// 						return new Response(200, {}, res);

+		// 					}else{

+

+		// 						console.log('err 1');

+		// 						let resp = new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}});

+		// 						console.log(resp);

+		// 						return resp;

+		// 					}

+		// 				}

+		// 			);

+		// 		}else{

+		// 			console.log('not 200')

+		// 			return new Response(400, {}, {errors: errors});

+		// 		}

+		// 	})

+		// 	.catch(err => {

+		// 		return new Response(200, {});

+		// 	});

+	}

+	// async get (id, params) {

+	// 	console.log("bpmn-upload: get")

+	// 	let errors = {};

+	// 	// Get List of Definition keys from Camunda

+

+	// 	// let options = {

+	// 	// 	url: this.options.app.get('camundaApi').url + 'otf/tcu/testDefinition/v1/processDefinitionKey/' + id,

+	// 	// 	headers: {

+	// 	// 		'Authorization': 'Basic ' + new Buffer(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword).toString('base64')

+	// 	// 	},

+	// 	// 	rejectUnauthorized: false

+	// 	// }

+

+	// 	// return await new Promise((resolve, reject) => {

+	// 	// 	request.post(options, (err, res, body) => {

+	// 	// 		if(err){

+	// 	// 			reject(err);

+	// 	// 		}

+	// 	// 		resolve(res);

+	// 	// 	});

+	// 	// }).then(

+	// 	// 	result => {

+	// 	// 		console.log(result);

+	// 	// 		if (result.statusCode === 200) {

+	// 	// 			//check to make sure they have access

+	// 	// 			params.query.$limit = '-1';

+	// 	// 			params.query.processDefinitionKey = id;

+	// 	// 			return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(params).then(

+	// 	// 				res => {

+	// 	// 					return new Response(200, {}, res);

+	// 	// 				},

+	// 	// 				err => {

+	// 	// 					return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})

+	// 	// 				}

+	// 	// 			);

+	// 	// 		}else if(result.statusCode == 404){

+	// 	// 			return new Response(400, {}, {errors: errors});

+	// 	// 		}else{

+	// 	// 			return new Response(result.statusCode, {}, {errors: errors});

+	// 	// 		}

+	// 	// 	}

+	// 	// ).catch(

+	// 	// 	err => {

+	// 	// 		console.log("Err: " + err)

+	// 	// 		//return new Response(200, {});

+	// 	// 		let newParams = Object.assign({}, params);

+	// 	// 		newParams.query.$limit = -1;

+	// 	// 		newParams.query.processDefinitionKey = id;

+	// 	// 		//console.log(params);

+	// 	// 		return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(newParams).then(

+	// 	// 			res => {

+	// 	// 				//return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})

+	// 	// 				return new Response(200, {}, res);

+	// 	// 			},

+	// 	// 			err => {

+	// 	// 				return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})

+	// 	// 			}

+	// 	// 		);

+	// 	// 	}

+	// 	// );

+

+	// 	return await axios.get(

+	// 		this.options.app.get('camundaApi').url + 'otf/tcu/testDefinition/v1/processDefinitionKey/' + id,

+	// 		{

+	// 			headers: {

+	// 				Authorization: 'Basic ' + new Buffer(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword).toString('base64')

+	// 			}

+	// 		})

+	// 		.then(result => {

+	// 			console.log(result);

+	// 			if (result.status === 200) {

+	// 				console.log('in here')

+	// 				//check to make sure they have access

+	// 				params.query.$limit = '-1';

+	// 				params.query.processDefinitionKey = id;

+	// 				return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(params).then(

+	// 					res => {

+	// 						return new Response(200, {}, res);

+	// 					}

+	// 				).catch(err => {

+	// 					console.log('err')

+	// 					return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})

+	// 				});

+	// 			}else if(result.status === 404){

+	// 				console.log('or here')

+	// 				return new Response(400, {}, {errors: errors});

+	// 			}else{

+	// 				return new Response(result.status, {}, {errors: errors});

+	// 			}

+	// 		})

+	// 		.catch(err => {

+	// 			console.log("Err: " + err)

+	// 			//return new Response(200, {});

+	// 			let newParams = Object.assign({}, params);

+	// 			newParams.query.$limit = -1;

+	// 			newParams.query.processDefinitionKey = id;

+	// 			//console.log(params);

+	// 			return this.options.app.services[this.options.app.get('base-path') + 'test-definitions'].find(newParams).then(

+	// 				res => {

+	// 					//return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})

+	// 					return new Response(200, {}, res);

+	// 				}

+	// 			).catch(err => {

+	// 				console.log('err 2')

+	// 				return new Response(400, {}, {errors: {processDefinitionKey: 'You do not have access to this process definition key'}})

+	// 			});

+	// 		});

+	// }

+

+	async create (data, params) {

+		let bpmn = new Bpmn(this.options.app, data, params);

+		return await bpmn.validate();

+	}

+

+	//validates then saves bpmn file and returns file meta data

+	async update (id, data, params) {

+		let bpmn = new Bpmn(this.options.app, data, params);

+		let res = await bpmn.validate();

+		if(res.statusCode != 200){

+			return res;

+		}

+		

+		let b = new Buffer(res.body.bpmnXml);

+		let r = new Readable();

+		r.push(b);

+		r.push(null);

+		//save new bpmn file and return

+		let formData = {

+			'file': {

+				value: r.read(),

+				options: {

+					filename: res.body.processDefinitionKey + '.bpmn'

+				}

+			}

+		};

+		let options = {

+			url: 'https://localhost/' + this.options.app.get('base-path') + 'file-transfer',

+			headers: {

+				'Authorization': params.headers.Authorization,

+				'Content-Type': "multipart/form-data"

+			},

+			rejectUnauthorized: false,

+			formData: formData

+		}

+

+		return await new Promise((resolve, reject) => {

+			request.post(options, (err, res, body) => {

+				if(err){

+					reject(err);

+				}

+				resolve(body);

+			});

+		}).then(

+			result => {

+				return result;

+			}

+		).catch(

+			err => {

+				return err;

+			}

+		);

+

+	}

+

+	async patch (id, data, params) {

+		return data;

+	}

+

+	async remove (id, params) {

+		return { id };

+	}

+

+	async parseAndUpload (data, params, method) {

+

+	}

+}

+

+module.exports = function (options) {

+	return new Service(options);

+};

+

+module.exports.Service = Service;

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
index 0000000..98dc42e
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.hooks.js
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt')],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..914fb26
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn-validate.service.js
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `bpmnValidate` service on path `/bpmn-validate`

+const createService = require('./bpmn-validate.class.js');

+const hooks = require('./bpmn-validate.hooks');

+

+module.exports = function (app) {

+	const paginate = app.get('paginate');

+

+	const options = {

+		paginate,

+		app

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'bpmn-validate',	createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'bpmn-validate');

+

+	service.hooks(hooks);

+};

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
index 0000000..620f9bd
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/bpmn-validate/bpmn.class.js
@@ -0,0 +1,360 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { parseString, Builder } = require('xml2js');

+const logger = require('../../../lib/logger');

+const Response = require('http-response-object');

+

+class Bpmn {

+    constructor(app, data, params){

+		this.delegates = [

+			{

+				name: 'vth',

+				regex: new RegExp('^(vth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),

+				delegate: '${callTestHeadDelegate}'

+			},

+			{

+				name: 'lvth',

+				regex: new RegExp('^(lvth)(:([a-zA-Z0-9_ -]+))(:([a-zA-Z0-9_ -]+))?$', 'i'),

+				topic: 'vth'

+			},

+			{

+				name: 'log',

+				regex: new RegExp('^UTIL:LogTestResult$', 'i'),

+				delegate: '${logTestResultDelegate}',

+			},

+			{

+				name: 'pflo',

+				regex: new RegExp('^PFLO(:(.+))?$', 'i'),

+				delegate: '${runTestInstanceDelegate}',

+				topic: 'testInstance'

+			},

+			{

+				name: 'SubFlow',

+				regex: new RegExp('^SUBFLOW(:(.+))?$', 'i'),

+				delegate: '${runTestInstanceDelegate}',

+				topic: 'testInstance'

+			},

+			{

+				name: 'dmaap',

+				regex: new RegExp('^PostResultsToDMaaP(:(.+))?$', 'i'),

+				delegate: '${postResultsToDMaaPDelegate}'

+			},

+			{

+				name: 'mail',

+				regex: new RegExp('^UTIL:SendMail(:(.+))?$', 'i'),

+				delegate: '${sendMailDelegate}'

+			}

+		];

+

+		this.serviceTasksNotAllowed = [

+			{

+				key: 'camunda:class'

+			}

+		]

+        

+        this.params = params;

+        this.data = data;

+        this.app = app;

+		this.parsedXMLtoJSON = null;

+		this.bpmnVthTaskIds = [];

+		this.bpmnPfloTaskIds = [];

+		this.processDefinitionKey = null;

+		this.errors = {};

+		this.hasLog = false; //1 log is required in each workflow

+		this.hasTestHeads = false;

+		

+    }

+

+    async validate(){

+		//convert bpmn to json

+		//console.log(this.data.testDefinition);

+		parseString(

+			this.data.testDefinition.bpmnInstances[this.data.testDefinition.currentVersion].bpmnXml,

+			(err, result) => {

+				if (err) {

+					logger.error(err);

+				}

+				this.parsedXMLtoJSON = Object.assign({}, result);

+            }

+		);

+

+        //If the bpmn was unable to be parsed, return error response

+        if (!this.parsedXMLtoJSON) {

+			return new Response(500, {}, { errors: { parsingError: { error: 'Failed to parse bpmn. Try Again.' } } });

+        }

+

+        //set temp process

+		var process = this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0];

+

+

+		// Not needed with new modeler

+        //If a process definition key was sent with the requrest, use it instead

+		if (this.data.testDefinition.processDefinitionKey && this.data.testDefinition.processDefinitionKey != '') {

+			this.processDefinitionKey = this.data.testDefinition.processDefinitionKey;

+		}else{

+		    this.processDefinitionKey = process.$.id;

+        }

+        

+        //Check to see if the process definition key is unique

+        let key = await this.app.services[this.app.get('base-path') + 'bpmn-validate'].get(this.processDefinitionKey, this.params).then();

+        if(key.statusCode != 200 && key.errors && key.errors.processDefinitionKey){

+            this.errors.processDefinitionKey = {

+                error: 'Process Definition Key has already been used',

+                key: this.processDefinitionKey

+            };

+        }

+        

+        // Verify start task(s) are async. Only start task(s) in main process

+		if (process['bpmn:startEvent']) {

+			for (var j = 0; j < process['bpmn:startEvent'].length; j++) {

+				var startEvent = process['bpmn:startEvent'][j];

+				if (startEvent.$['camunda:asyncBefore'] != 'true') {

+					this.errors.startEvent = { error: 'Start Event, ' + startEvent.$.id + ', is not async' };

+				}

+			}

+		} else {

+			this.errors.startEvent = { error: 'Workflow must have a start even' };

+        }

+        

+        //Find all of the task boxes that need to be changed (recursive)

+        await this.findTasks(this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0]);

+        

+        // If log task did not exist, log

+		if (!this.hasLog) {

+			this.errors.required = { error: 'No LogSetResult task. One is required.' };

+        }

+        

+        // If errors, return them before creating an instance in the database

+		if (

+			this.errors.processDefinitionKey ||

+			this.errors.notFound ||

+			this.errors.testHead ||

+			this.errors.permissions ||

+			this.errors.required ||

+			this.errors.startEvent

+		) {

+			return new Response(400, {}, { bpmnVthTaskIds: this.bpmnVthTaskIds, errors: this.errors });

+		}

+

+        //set the new process Definition key

+        //console.log('END Process Key: ' + this.processDefinitionKey);

+		this.parsedXMLtoJSON['bpmn:definitions']['bpmn:process'][0].$.id = this.processDefinitionKey;

+

+        //build xml from the json object

+		var xmlBuilder = new Builder();

+        var xmlToSend = xmlBuilder.buildObject(this.parsedXMLtoJSON);

+        

+		//console.log(this.bpmnVthTaskIds);

+		

+		let response = { 

+			bpmnXml: xmlToSend, 

+			bpmnVthTaskIds: this.bpmnVthTaskIds, 

+			bpmnPfloTaskIds: this.bpmnPfloTaskIds, 

+			processDefinitionKey: this.processDefinitionKey, 

+		};

+

+		//if there are errors

+		if(JSON.stringify(this.errors) != "{}"){

+			response.errors = this.errors

+		}

+

+		return new Response(200, {}, response);

+        

+    }

+

+    async findTasks (process) {

+		//If there are service tasks in the diagram

+		if(process['bpmn:serviceTask']){

+			//console.log('has service task');

+			// Go through all of the service task boxes

+			for (let j = 0; j < process['bpmn:serviceTask'].length; j++) {

+				//console.log(process['bpmn:serviceTask'][j])

+

+				//check that the service task is not on the DO NOT ALLOW list

+				for(let n = 0; n < this.serviceTasksNotAllowed.length; n++){

+					//check cammunda keys

+					if(process['bpmn:serviceTask'][j].$[this.serviceTasksNotAllowed[n].key]){

+						if(!this.errors.permissions){

+							this.errors.permissions = [];

+						}

+						this.errors.permissions.push({error: this.serviceTasksNotAllowed[n].key + ' is not allowed.'})

+					}

+				}

+

+				//Clear any user defined delegate expressions

+				if(process['bpmn:serviceTask'][j].$['camunda:delegateExpression']){

+					process['bpmn:serviceTask'][j].$['camunda:delegateExpression'] = '';

+				}

+				

+				//Go through all the delegates that are defined by OTF (in constructor)

+				for (let d = 0; d < this.delegates.length; d++){

+					var match = null;

+					

+					if(match = process['bpmn:serviceTask'][j].$.name.match(this.delegates[d].regex)){

+						//console.log(match)

+						switch(this.delegates[d].name){

+							case 'vth':

+							case 'cvth':

+							case 'lvth':

+								await this.checkTestHead(match, process['bpmn:serviceTask'][j]);

+								break;

+

+							case 'pflo':

+								let temp = {bpmnPfloTaskId: process['bpmn:serviceTask'][j].$.id};

+								if(match[2]){

+									temp['label'] = match[2];

+								}

+								this.bpmnPfloTaskIds.push(temp);

+								break;

+							

+							case 'log':

+								this.hasLog = true;

+								break;

+							

+						}

+

+						if(this.delegates[d].topic){

+							process['bpmn:serviceTask'][j].$['camunda:type'] = 'external';

+							process['bpmn:serviceTask'][j].$['camunda:topic'] = this.delegates[d].topic;

+						}else{

+							process['bpmn:serviceTask'][j].$['camunda:delegateExpression'] = this.delegates[d].delegate;

+						}

+

+						break;

+

+					}

+				}

+

+			}

+		} //end if service task

+

+		if(process['bpmn:task']){

+			//console.log('has task')

+			//init service task array 

+			if(!process['bpmn:serviceTask']){

+				process['bpmn:serviceTask'] = [];

+			}

+

+			// Go through all of the task boxes

+			for (let j = 0; j < process['bpmn:task'].length; j++) {

+				//console.log(process['bpmn:task'][j])

+

+				for (let d = 0; d < this.delegates.length; d++){

+					var match = null;

+					

+					if(match = process['bpmn:task'][j].$.name.match(this.delegates[d].regex)){

+						//console.log(match)

+						switch(this.delegates[d].name){

+							case 'vth':

+							case 'cvth':

+							case 'lvth':

+								await this.checkTestHead(match, process['bpmn:task'][j]);

+								break;

+							

+							case 'pflo':

+								let temp = {bpmnPfloTaskId: process['bpmn:task'][j].$.id};

+								if(match[2]){

+									temp['label'] = match[2];

+								}

+								this.bpmnPfloTaskIds.push(temp);

+								break;

+							

+							case 'log':

+								this.hasLog = true;

+								break;

+							

+						}

+

+						let task = {

+							$: {

+								id: process['bpmn:task'][j].$.id,

+								name: process['bpmn:task'][j].$.name,

+							},

+							'bpmn:incoming': process['bpmn:task'][j]['bpmn:incoming'],

+							'bpmn:outgoing': process['bpmn:task'][j]['bpmn:outgoing']

+						}

+

+						if(this.delegates[d].topic){

+							task.$['camunda:type'] = 'external';

+							task.$['camunda:topic'] = this.delegates[d].topic;

+						}else{

+							task.$['camunda:delegateExpression'] = this.delegates[d].delegate;

+						}

+

+						process['bpmn:serviceTask'].push(task);

+

+						process['bpmn:task'].splice(j, 1);

+						j--;

+

+						break;

+

+					}

+				}

+

+			}

+

+		}

+

+		//If subprocess, find tasks

+		if(process['bpmn:subProcess']){

+			for(let p = 0; p < process['bpmn:subProcess'].length; p++){

+				await this.findTasks(process['bpmn:subProcess'][p]);

+			}

+		}

+		

+	}

+

+	async checkTestHead(match, task){

+		if (match.length >= 4) {

+			match[3] = '^' + match[3] + '$';

+			this.params.query = { testHeadName: new RegExp(match[3], 'i')};

+			delete this.params.paginate;

+			//console.log(this.params);

+			await this.app.services[this.app.get('base-path') + 'test-heads'].find(this.params)

+				.then(result => {

+					if (result.total > 1) {

+						// there should only be one test head found, else there is an error in the database

+						if (!this.errors.testHeads) {

+							this.errors.testHeads = [];

+						}

+						this.errors.testHeads.push({ error: result.total + ' test heads named: ' + match[3] });

+					}

+

+					if (result.total == 0) {

+						if(!this.errors.permissions){

+							this.errors.permissions = []

+						}

+						this.errors.permissions.push({ error: 'You do not have access to test head: ' + match[3] });

+					} else {

+						this.bpmnVthTaskIds.push({ testHead: result.data[0], bpmnVthTaskId: task.$.id });

+					}

+				})

+				.catch(err => {

+					//console.log(err);

+					this.errors.notFound = { error: 'Test head "' + match[3] + '" does not exist' };

+				});

+		} else if (match.length > 0) { // no test head name supplied

+

+		}

+	}

+

+}

+module.exports = function (app, data, params) {

+	return new Bpmn(app, data, params);

+};

+

+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
index 0000000..f5b6867
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/execute/execute.class.js
@@ -0,0 +1,132 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const request = require('request');

+const Response = require('http-response-object');

+const logger = require('../../../lib/logger');

+const util = require('../../../lib/otf-util');

+const errors = require('@feathersjs/errors');

+

+class Service {

+	constructor (options) {

+		this.options = options || {};

+	}

+

+	async find (params) {

+		return [];

+    }

+    

+	async get (id, params) {

+

+	}

+

+	async create (data, params) {

+		

+        let id = data._id;

+		delete data._id;

+		delete data.createdBy;

+		

+        let options = {

+			url: this.options.app.get('serviceApi').url + 'testInstance/execute/v1/id/' + id,

+			headers: {

+				'Authorization': 'Basic ' + util.base64Encode(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword),

+				'Content-Type': "application/json"

+			},

+            rejectUnauthorized: false,

+            body: JSON.stringify(data)

+		}

+

+		return await new Promise((resolve, reject) => {

+			request.post(options, (err, res, body) => {

+				if(err){

+					reject(err);

+				}

+				if(res.body){

+					res.body = JSON.parse(res.body);

+					if(res.body.statusCode != 200){

+						reject(res.body);

+					}

+					resolve(res.body);

+				}else{

+					reject(res);

+				}

+				

+			});

+		}).then(

+			res => {

+                return res;

+            }

+		).catch(

+			err => {

+				return err;

+			}

+		);

+	}

+

+	async update (id, data, params) {

+		return data;

+	}

+

+	async patch (id, data, params) {

+		return data;

+	}

+

+	async remove (id, params) {

+

+		let execution = await this.options.app.services[this.options.app.get('base-path') + 'test-executions'].get(id, { query: { $select: ['processInstanceId']}});

+		

+		if(!execution.processInstanceId){

+			throw new errors.GeneralError('Could not find the execution process instance id');

+		}

+

+		let options = {

+			url: this.options.app.get('camundaApi').url + 'otf/tcu/delete-process-instance/v1/' + execution.processInstanceId,

+			headers: {

+				'Authorization': 'Basic ' + util.base64Encode(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword),

+				'Content-Type': "application/json"

+			},

+			rejectUnauthorized: false

+		}

+

+		return await new Promise((resolve, reject) => {

+			request.delete(options, (err, res, body) => {

+				if(err){

+					reject(err);

+				}

+				if(res.body){

+					res.body = JSON.parse(res.body);

+				}

+				resolve(res);

+			});

+		}).then(

+			res => {

+				return res;

+			}

+		).catch(

+			err => {

+				console.log(err);

+			}

+		);

+	}

+

+

+}

+

+module.exports = function (options) {

+	return new Service(options);

+};

+

+module.exports.Service = Service;

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
index 0000000..f64d812
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/execute/execute.hooks.js
@@ -0,0 +1,93 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { permissions, limitFields } = require('../../hooks/permissions/permissions');

+const errors = require('@feathersjs/errors');

+const throwError = require('../../hooks/throw');

+const { disallow } = require('feathers-hooks-common');

+const canExecute = function(){

+	return async (context) => {

+		let id = context.id || context.data._id;

+		//must have an _id

+		if(!id){

+			if(context.method == 'create')

+				throw new errors.BadRequest("'_id' and 'asyncTopic' is required to execute a test instance");

+			else

+				throw new errors.BadRequest("An id must be provided to cancel an execution")

+		}

+

+		let testInstanceId = id;

+

+		if(context.method == 'remove'){

+			let execution = await context.app.services[context.app.get('base-path') + 'test-executions'].get(id, {provider: undefined, query: { $select: ['historicTestInstance._id']}});

+			testInstanceId = execution.historicTestInstance._id;

+		}

+

+		//get group id of the test instance that is being executed

+		let testInstance = await context.app.services[context.app.get('base-path') + 'test-instances'].get(testInstanceId, {query: { $select: ['groupId', 'testDefinitionId', 'disabled'] } });

+

+		//check if its locked

+		let testDefinition = await context.app.services[context.app.get('base-path') + 'test-definitions'].get(testInstance.testDefinitionId, {query: { $select: ['disabled'] } });

+

+		if((testInstance.disabled || testDefinition.disabled) && context.method == 'create'){

+			throw new errors.Unavailable('The test instance or definition is locked.');

+		}

+

+		testInstance = new context.app.services[context.app.get('base-path') + 'test-instances'].Model(testInstance);

+		if(context.params.ability.cannot('execute', testInstance)){

+			throw new errors.Forbidden(`You are not allowed to execute this instance.`);

+		}

+	}

+}

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt'), permissions('execute')],

+		find: [ throwError(new errors.MethodNotAllowed()) ],

+		get: [ throwError(new errors.MethodNotAllowed())],

+		create: [

+			(context) => {

+				context.data.executorId = context.params.user._id;

+				return context;

+			},

+			canExecute()

+		],

+		update: [ throwError(new errors.MethodNotAllowed()) ],

+		patch: [ throwError(new errors.MethodNotAllowed()) ],

+		remove: [canExecute()]

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..dff9b69
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/execute/execute.service.js
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `groups` service on path `/groups`

+const createService = require('./execute.class');

+const hooks = require('./execute.hooks');

+

+module.exports = function (app) {

+	const paginate = app.get('paginate');

+

+	const options = {

+		app,

+		paginate

+	};

+

+	app.use(app.get('base-path') + 'execute', createService(options));

+

+	const service = app.service(app.get('base-path') + 'execute');

+

+	service.hooks(hooks);

+};

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
index 0000000..74a5a73
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/feedback/feedback.class.js
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+

+class Service {

+	constructor (options) {

+		this.options = options || {};

+	}

+

+	// function getLink(type, hash) {

+	// 	const url = 'http://localhost:443/' + type + '?token=' + hash

+	// 	return url

+	// }

+

+	async find (params) {

+		return [];

+	}

+

+	// Check process definition key to see if unique

+	async get (id, params) {

+

+	}

+

+	async create (data, params) {

+

+

+

+	}

+

+	async update (id, data, params) {

+		return data;

+	}

+

+	async patch (id, data, params) {

+		return data;

+	}

+

+	async remove (id, params) {

+		return { id };

+	}

+

+

+}

+

+module.exports = function (options) {

+	return new Service(options);

+};

+

+module.exports.Service = Service;

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
index 0000000..7820300
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/feedback/feedback.hooks.js
@@ -0,0 +1,78 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { disallow } = require('feathers-hooks-common');

+

+module.exports = {

+	before: {

+		all: [ ],

+		find: [],

+		get: [],

+		create: [

+			authenticate('jwt'),

+			context => {

+				let sender = context.app.get('otf').email;

+				let data = context.data['data'];

+				let message = data['message'];

+				let user = context.params.user;

+

+				let feedback = "Email: " + user['email'] + "</br>" +

+								"First Name: " + user['firstName'] + "</br>" +

+								"Last Name: " + user['lastName'] + "</br>" +

+								"Message: " + message + "</br>" +

+								"Date: " + new Date();

+					let email = {

+					from: sender,

+					to: sender,

+					subject: 'Feedback',

+					html: feedback

+				}

+

+				return context.app.service(context.app.get('base-path') + 'mailer').create(email).then(function (result) {

+					console.log('Sent email', result)

+				}).catch(err => {

+					console.log('Error sending email: ', email, err)

+				})

+

+

+			}

+		],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..7ae61ed
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/feedback/feedback.service.js
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `bpmnValidate` service on path `/bpmn-validate`

+const createService = require('./feedback.class.js');

+const hooks = require('./feedback.hooks');

+

+module.exports = function (app) {

+	const paginate = app.get('paginate');

+

+	const options = {

+		paginate,

+		app

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'feedback', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'feedback');

+

+	service.hooks(hooks);

+};

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
index 0000000..0ac6670
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/file-transfer/file-transfer.class.js
@@ -0,0 +1,177 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const Response = require('http-response-object');

+const Readable = require('stream').Readable;

+const mongooseGridFS = require('mongoose-gridfs');

+const AdmZip = require('adm-zip');

+const errors = require('@feathersjs/errors');

+

+class Service {

+	constructor (options) {

+		this.options = options || {};

+		this.mongoose = this.options.app.get('mongooseClient');

+		this.gridfs = mongooseGridFS({

+			collection: 'fs',

+			model: 'File',

+			mongooseConnection: this.mongoose.connection

+		});

+		this.FileModel = this.gridfs.model;

+	}

+

+	async find (params) {

+		return new Response(200, {});

+	}

+

+	async get (id, params) {

+		let content = await this.callReadFile(id).then(res => {

+			return res;

+		});

+

+		if(params.query && params.query.robot){

+			content = await this.createRobotResponse(content);

+		}

+		return content;

+	}

+

+	async create (data, params) {

+        const files = params.files;

+

+        if (!files || files.length === 0) {

+            throw new BadRequest("No files found to upload")

+        }

+

+        let promises = [];

+

+        files.forEach(file => {

+            let promise = new Promise( (resolve, reject) => {

+

+                let stream = new Readable();

+                stream.push(file.buffer);

+                stream.push(null);

+

+            	this.FileModel.write(

+            		{

+            			filename: file.originalname,

+            			contentType: file.mimeType

+            		},

+            		stream,

+            		function (error, savedAttachment) {

+            			if (error) {

+            				logger.error(error);

+            				reject(error);

+            			} else {

+                            stream.destroy();

+                            resolve(savedAttachment);

+            			}

+                    }

+                );

+

+            })

+

+            promises.push(promise);

+        });

+

+        const result = await Promise.all(promises);

+        

+        return result;

+	}

+

+	async update (id, data, params) {

+		return new Response(200, {});

+	}

+

+	async patch (id, data, params) {

+		return new Response(200, {});

+	}

+

+	async remove (id, params) {

+		let err = await this.callUnlinkFile(id).then(err => {

+            return err;

+        });

+

+        if(err){

+            throw errors.GeneralError(err);

+        }        

+

+        return new Response(200, {});

+	}

+

+	readFile (id) {

+		return new Promise(resolve => {

+			// FileModel.readById(context.id, (err, content) => resolve(content));

+			let stream = this.FileModel.readById(id);

+	

+			stream.on('error', (err) => resolve(err));

+			stream.on('data', (content) => resolve(content));

+			stream.on('close', (res) => resolve(res));

+			// api.on(event, response => resolve(response));

+		});

+	}

+	

+	async callReadFile (id) {

+		return this.readFile(id);

+	}

+	

+	async createRobotResponse(content){

+	

+		let re;

+	

+		await new Promise((resolve, reject) => {

+			let newObj = {};

+			let read = new Readable();

+			read.push(content);

+			read.push(null);

+			let z = new AdmZip(content);

+			let entries = z.getEntries();

+			entries.forEach(zipEntry => {

+				newObj[zipEntry.name] = zipEntry.getData().toString('utf8');

+				// console.log(zipEntry.toString()); // outputs zip entries information

+				// console.log(zipEntry.getData().toString('utf8')); 

+			});

+			resolve(newObj);

+		}).then(res => {

+			re = res;

+			//console.log(re);

+		}).catch(err => {

+			console.log(err);

+		});

+	

+		return re;

+	}

+

+	unlinkFile(id) {

+	

+		return new Promise(resolve => {

+			

+			//FileModel.readById(context.id, (err, content) => resolve(content));

+			this.FileModel.unlinkById(id, (err) => resolve(err));

+			//api.on(event, response => resolve(response));

+		});

+	}

+	

+	async callUnlinkFile(id) {

+		return await this.unlinkFile(id);

+	}

+}

+

+

+

+module.exports = function (options) {

+	return new Service(options);

+};

+

+module.exports.Service = Service;

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
index 0000000..8e48bfe
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/file-transfer/file-transfer.hooks.js
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { disallow } = require('feathers-hooks-common');

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt')],

+		find: [disallow()],

+		get: [],

+		create: [],

+		update: [disallow()],

+		patch: [disallow()],

+		remove: []

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..961bde4
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/file-transfer/file-transfer.service.js
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const createService = require('./file-transfer.class');

+const hooks = require('./file-transfer.hooks');

+

+const multipartMiddleware = require('multer')();

+

+module.exports = function (app) {

+	const paginate = app.get('paginate');

+

+	const options = {

+		name: 'files',

+		paginate,

+		app

+	};

+

+	// Initialize our service with any options it requires

+	app.use(

+		app.get('base-path') + 'file-transfer',

+

+		multipartMiddleware.any(),

+

+		function (req, res, next) {

+			req.feathers.files = req.files;

+			next();

+		},

+

+		createService(options)

+	);

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'file-transfer');

+

+	service.hooks(hooks);

+};

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
index 0000000..21d17ac
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/files/files.hooks.js
@@ -0,0 +1,52 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { disallow } = require('feathers-hooks-common');

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt')],

+		find: [],

+		get: [],

+		create: [

+			disallow()

+		],

+		update: [disallow()],

+		patch: [disallow()],

+		remove: [disallow()]

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..0fc3476
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/files/files.service.js
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const createService = require('feathers-mongoose');

+const createModel = require('../../models/file.model');

+const hooks = require('./files.hooks');

+

+module.exports = function (app) {

+	const Model = createModel(app);

+

+	const options = {

+		Model

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'files', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'files');

+

+	service.hooks(hooks);

+};

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
index 0000000..c570596
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/groups/groups.hooks.js
@@ -0,0 +1,51 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { groupFilter } = require('../../hooks/filters.js');

+const { permissions } = require('../../hooks/permissions/permissions');

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt')],

+		find: [ groupFilter() ],

+		get: [ groupFilter() ],

+		create: [ permissions('groups') ],

+		update: [ permissions('groups') ],

+		patch: [ permissions('groups') ],

+		remove: [ permissions('groups') ]

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..bee071b
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/groups/groups.service.js
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `groups` service on path `/groups`

+const createService = require('feathers-mongoose');

+const createModel = require('../../models/groups.model');

+const hooks = require('./groups.hooks');

+

+module.exports = function (app) {

+	const Model = createModel(app);

+	const paginate = app.get('paginate');

+

+	const options = {

+		Model,

+		paginate

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'groups', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'groups');

+

+	service.hooks(hooks);

+};

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
index 0000000..28dfcdf
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/health/health.class.js
@@ -0,0 +1,106 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const Response = require('http-response-object');

+const request = require('request');

+

+/* eslint-disable no-unused-vars */

+class Service {

+	constructor (options) {

+		this.options = options || {};

+	}

+

+	async find (params) {

+		return new Response(200, {});

+	}

+

+	async get (id, params) {

+		if(id == 'tcu-engine'){

+			let options = {

+				url: this.options.app.get('camundaApi').url + 'otf/health/v1',

+				rejectUnauthorized: false

+			}

+			

+			return await new Promise((resolve, reject) => {

+				request.get(options, (err, res, body) => {

+					if(err){

+						reject(err);

+					}

+					resolve(res);

+				});

+			}).then(

+				res => {

+					return res;

+				}

+			).catch(

+				err => {

+					return new Response(500, {}, err);

+				}

+			);

+		}else if(id == 'tcu-api'){

+			let options = {

+				url: this.options.app.get('serviceApi').url + 'health/v1',

+				rejectUnauthorized: false

+			}

+			

+			return await new Promise((resolve, reject) => {

+				request.get(options, (err, res, body) => {

+					if(err){

+						reject(err);

+					};

+					resolve(res);

+				});

+			}).then(

+				res => {

+					return res;

+				}

+			).catch(

+				err => {

+					return new Response(500, {}, err);

+				}

+			);

+		}else{

+			return new Response(200, {});

+		}

+		

+	}

+

+	async create (data, params) {

+		if (Array.isArray(data)) {

+			return Promise.all(data.map(current => this.create(current, params)));

+		}

+

+		return new Response(200, {});

+	}

+

+	async update (id, data, params) {

+		return new Response(200, {});

+	}

+

+	async patch (id, data, params) {

+		return new Response(200, {});

+	}

+

+	async remove (id, params) {

+		return new Response(200, {});

+	}

+}

+

+module.exports = function (options) {

+	return new Service(options);

+};

+

+module.exports.Service = Service;

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
index 0000000..ad4601b
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/health/health.hooks.js
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+module.exports = {

+	before: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..8b66fd0
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/health/health.service.js
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `health` service on path `/health`

+const createService = require('./health.class.js');

+const hooks = require('./health.hooks');

+

+module.exports = function (app) {

+	const paginate = app.get('paginate');

+

+	const options = {

+		paginate,

+		app

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('path') + 'health/v1', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('path') + 'health/v1');

+

+	service.hooks(hooks);

+};

diff --git a/otf-frontend/server/src/feathers/services/index.js b/otf-frontend/server/src/feathers/services/index.js
new file mode 100644
index 0000000..cccf259
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/index.js
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const users = require('./users/users.service.js');

+const groups = require('./groups/groups.service.js');

+const testHeads = require('./test-heads/test-heads.service.js');

+const testInstances = require('./test-instances/test-instances.service.js');

+const testExecutions = require('./test-executions/test-executions.service.js');

+const testDefinitions = require('./test-definitions/test-definitions.service.js');

+const jobs = require('./jobs/jobs.service.js');

+const health = require('./health/health.service.js');

+const bpmnUpload = require('./bpmn-upload/bpmn-upload.service.js');

+const bpmnValidate = require('./bpmn-validate/bpmn-validate.service.js');

+const testExecutionStatus = require('./test-execution-status/test-execution-status.service.js');

+const testExecutionController = require('../../agenda/controllers/test-execution-controller');

+const mailer = require('./mailer/mailer.service.js');

+const authManagement = require('./auth-management/auth-management.service.js');

+const feedback = require('./feedback/feedback.service.js');

+const fileTransfer = require('./file-transfer/file-transfer.service.js');

+const files = require('./files/files.service.js');

+const execute = require('./execute/execute.service.js');

+const messages = require('./messages/messages.service')

+

+module.exports = function (app) {

+	app.configure(users);

+	app.configure(files);

+	app.configure(fileTransfer)

+	app.configure(groups);

+	app.configure(testHeads);

+	app.configure(testInstances);

+	app.configure(testExecutions);

+	app.configure(testDefinitions);

+	app.configure(execute);

+	app.configure(messages);

+	app.configure(jobs);

+	app.configure(health);

+	app.configure(bpmnUpload);

+	app.configure(bpmnValidate);

+	app.configure(testExecutionStatus);

+	app.configure(testExecutionController);

+	app.configure(mailer);

+	app.configure(authManagement);

+	app.configure(feedback);

+};

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
index 0000000..1571dc1
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/jobs/jobs.class.js
@@ -0,0 +1,16 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

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
index 0000000..f119833
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/jobs/jobs.hooks.js
@@ -0,0 +1,218 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { iff, disallow } = require('feathers-hooks-common');

+const logger = require('../../../lib/logger');

+const request = require('request-promise');

+const agendaJobPopulate = require('../../hooks/agendaJobPopulate');

+const utils = require('../../../lib/otf-util');

+const axios = require('axios');

+const util = require('../../../lib/otf-util');

+const checkLocks = require('../../hooks/checkLocks');

+const { permissions, limitFields } = require('../../hooks/permissions/permissions');

+const errors = require('@feathersjs/errors');

+

+const canExecute = function(){

+	return async (context) => {

+		let id = context.id || context.data.testInstanceId;

+

+		let testInstanceId = id;

+

+		if(context.method == 'remove'){

+			let job = await context.app.services[context.app.get('base-path') + 'jobs'].get(id, {provider: undefined});

+			console.log(job)

+			testInstanceId = job.data.testSchedule._testInstanceId;

+			console.log(testInstanceId)

+		}

+

+		//get group id of the test instance that is being executed

+		let testInstance = await context.app.services[context.app.get('base-path') + 'test-instances'].get(testInstanceId, {provider: undefined, query: { $select: ['groupId', 'testDefinitionId', 'disabled'] } });

+

+		//check if its locked

+		let testDefinition = await context.app.services[context.app.get('base-path') + 'test-definitions'].get(testInstance.testDefinitionId, {provider: undefined, query: { $select: ['disabled'] } });

+

+		if((testInstance.disabled || testDefinition.disabled) && context.method == 'create'){

+			throw new errors.Unavailable('The test instance or definition is locked.');

+		}

+

+		testInstance = new context.app.services[context.app.get('base-path') + 'test-instances'].Model(testInstance);

+		if(context.params.ability.cannot('execute', testInstance)){

+			throw new errors.Forbidden(`You are not allowed to execute this instance.`);

+		}

+	}

+}

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt')],

+		find: [

+			async function(context){

+				if(context.params.query.testInstanceId){

+					const mongoose = context.app.get('mongooseClient');

+					const toObjectId = v => mongoose.Types.ObjectId(v);

+

+					let Model = context.app.service(context.app.get('base-path') + 'jobs').Model;

+					const conditions = [{

+						$match:{

+							 "data.testSchedule._testInstanceId": toObjectId(context.params.query.testInstanceId),

+							 "nextRunAt":  {

+								 $ne: null

+							 }//{

+							// 	"testSchedule": {

+							// 		"_testInstanceId": toObjectId(context.params.query.testInstanceId)

+							// 	}

+							// }

+						}

+					}];

+

+					await new Promise(function(resolve, reject){

+						Model.aggregate(conditions).exec(function(error, result){

+							if(error){

+								reject(error);

+							}

+							resolve(result);

+						});

+					}).then(result => {

+						if(result.length){

+							if(result.length == 1){

+								context.params.query._id = result[0]._id;

+							}else if(result.length == 0){

+								//do nothing

+							}else{

+								let ids = [];

+								result.forEach(elem => {

+									ids.push(elem._id);

+								});

+								context.params.query._id = {

+									$in: ids

+								}

+							}

+						}else{

+							context.params.query._id = result._id;

+						}

+					}).catch(err => {

+						console.log(err);

+					});

+

+					delete context.params.query.testInstanceId;

+				}

+				return context;

+			}

+		],

+		get: [],

+		create: [

+			permissions('jobs'),

+			(context) => { console.log("AFTER PERMISSIONS")},

+			canExecute(), 

+			async (context) => {

+				const fullUrl = 'https://localhost/' + context.app.get('base-path') + 'schedule-test';

+

+				context.data.executorId = context.params.user._id;

+

+				await request({

+					method: 'post',

+					url: fullUrl,

+					body: JSON.stringify(context.data),

+					headers: {

+						'Content-Type': 'application/json',

+						'Authorization': 'Basic ' +

+							util.base64Encode(

+								context.app.get('serviceApi').aafId + ':' +

+								context.app.get('serviceApi').aafPassword)

+					},

+					rejectUnauthorized: false

+				}, function (err, res, body) {

+					if (err) {

+						logger.error(err);

+					}

+

+					if (body) {

+						context.result = JSON.parse(body);

+					}

+

+				});

+

+				return context;

+			}

+		],

+		update: [],

+		patch: [],

+		remove: [

+			permissions('jobs'),

+			canExecute(),

+			async function (context) {

+			const fullUrl = 'https://localhost/' + context.app.get('base-path') + 'cancel-test';

+

+			if (context.id == null || context.params.user._id == null ||

+				utils.isValidObjectId(context.id) || utils.isValidObjectId(context.params.user._id)) {

+				context.result = {

+					status: 400,

+					message: 'Request is invalid.'

+				};

+			}

+

+			const postData = {

+				jobId: context.id,

+				executorId: context.params.user._id

+			};

+

+			// console.log(JSON.stringify(postData));

+

+			await request({

+				method: 'post',

+				url: fullUrl,

+				body: JSON.stringify(postData),

+				headers: {

+					'Content-Type': 'application/json',

+					'Authorization': 'Basic ' +

+						util.base64Encode(

+							context.app.get('serviceApi').aafId + ':' +

+							context.app.get('serviceApi').aafPassword)

+				},

+				rejectUnauthorized: false

+			}, function (err, res, body) {

+				if (err) {

+					logger.error(err);

+				}

+

+				context.result = JSON.parse(body);

+			});

+

+			return context;

+		}]

+	},

+

+	after: {

+		all: [],

+		find: [agendaJobPopulate()],

+		get: [agendaJobPopulate()],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..b444ed4
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/jobs/jobs.service.js
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `groups` service on path `/groups`

+const createService = require('feathers-mongoose');

+const createModel = require('../../models/jobs.model');

+const hooks = require('./jobs.hooks');

+

+module.exports = function (app) {

+	const Model = createModel(app);

+	const paginate = app.get('paginate');

+

+	const options = {

+		Model,

+		app,

+		paginate

+	};

+

+	app.use(app.get('base-path') + 'jobs', createService(options));

+

+	const service = app.service(app.get('base-path') + 'jobs');

+

+	service.hooks(hooks);

+};

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
index 0000000..52d9ee8
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/mailer/mailer.class.js
@@ -0,0 +1,75 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const axios = require('axios');

+const Response = require('http-response-object');

+const logger = require('../../../lib/logger');

+const util = require('../../../lib/otf-util');

+const beautify = require('json-beautify');

+const sendmail = require('sendmail')();

+

+class Service {

+	constructor (options) {

+		this.options = options || {};

+	}

+

+	// function getLink(type, hash) {

+	// 	const url = 'http://localhost:443/' + type + '?token=' + hash

+	// 	return url

+	// }

+

+	async find (params) {

+		return [];

+	}

+

+	// Check process definition key to see if unique

+	async get (id, params) {

+

+	}

+

+	async create (data, params) {

+

+		//send initial email for verification

+		//add token to user in database

+		sendmail(data, function(err, reply) {

+	      console.log(err && err.stack);

+	    });

+

+

+	}

+

+	async update (id, data, params) {

+		return data;

+	}

+

+	async patch (id, data, params) {

+		return data;

+	}

+

+	async remove (id, params) {

+		return { id };

+	}

+

+	async parseAndUpload (data, params, method) {

+

+	}

+}

+

+module.exports = function (options) {

+	return new Service(options);

+};

+

+module.exports.Service = Service;

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
index 0000000..e7aed9d
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/mailer/mailer.hooks.js
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { disallow } = require('feathers-hooks-common');

+

+module.exports = {

+	before: {

+		all: [ disallow('external')],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..62de2e6
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/mailer/mailer.service.js
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `bpmnValidate` service on path `/bpmn-validate`

+const createService = require('./mailer.class.js');

+const hooks = require('./mailer.hooks');

+

+module.exports = function (app) {

+	const paginate = app.get('paginate');

+

+	const options = {

+		paginate,

+		app

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'mailer', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'mailer');

+

+	service.hooks(hooks);

+};

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
index 0000000..2076ba9
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/messages/messages.class.js
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const request = require('request');

+const Response = require('http-response-object');

+const logger = require('../../../lib/logger');

+const util = require('../../../lib/otf-util');

+class Service {

+	constructor (options) {

+		this.options = options || {};

+	}

+

+	async find (params) {

+		return [];

+    }

+    

+	async get (id, params) {

+

+	}

+

+	async create (data, params) {

+        return data;

+	}   

+

+	async update (id, data, params) {

+		return data;

+	}

+

+	async patch (id, data, params) {

+		return data;

+	}

+

+	async remove (id, params) {

+		

+	}

+

+

+}

+

+module.exports = function (options) {

+	return new Service(options);

+};

+

+module.exports.Service = Service;

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
index 0000000..c82ac39
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/messages/messages.hooks.js
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const logger = require('../../../lib/logger');

+const request = require('request-promise');

+const agendaJobPopulate = require('../../hooks/agendaJobPopulate');

+const utils = require('../../../lib/otf-util');

+const axios = require('axios');

+const util = require('../../../lib/otf-util');

+

+module.exports = {

+	before: {

+		all: [],

+		find: [],

+		get: [],

+		create: [

+			

+		],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..75a221c
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/messages/messages.service.js
@@ -0,0 +1,37 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `testInstances` service on path `/test-instances`

+const createService = require('./messages.class');

+const hooks = require('./messages.hooks');

+const io = require('socket.io-client');

+const socket = io();

+ 

+module.exports = function (app) {

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'messages', createService());

+

+	// Get our initialized service so that we can register hooks

+    const service = app.service(app.get('base-path') + 'messages');

+    service.on('created', data => {

+        app.io.sockets.emit('message', 'service')

+    })

+

+    

+

+	service.hooks(hooks);

+};

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
index 0000000..9e33eb2
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-definitions/test-definitions.hooks.js
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const {permissions, limitFields } = require('../../hooks/permissions/permissions.js');

+const convertToYAML = require('../../hooks/convertToYAML.js');

+const convertJY = require('../../hooks/convertToYAMLRecursive');

+const deleteVersion = require('../../hooks/delete-version.js');

+const deleteDefinition = require('../../hooks/delete-definition.js');

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt'), permissions('testDefinitions')],

+		find: [],

+		get: [],

+		create: [

+			function (context) {

+				context.data.creatorId = context.params.user._id;

+				return context;

+			},

+			convertJY('json')

+		],

+		update: [convertJY('json')],

+		patch: [deleteVersion(), convertJY('json')],

+		remove: [

+			deleteDefinition()

+		]

+	},

+

+	after: {

+		all: [],

+		find: [convertToYAML()],

+		get: [convertToYAML()],

+		create: [convertToYAML()],

+		update: [convertToYAML()],

+		patch: [convertToYAML()],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..31b8b0a
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-definitions/test-definitions.service.js
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `testDefinition` service on path `/test-definition`

+const createService = require('feathers-mongoose');

+const createModel = require('../../models/test-definitions.model');

+const hooks = require('./test-definitions.hooks');

+

+module.exports = function (app) {

+	const Model = createModel(app);

+	const paginate = app.get('paginate');

+

+	const options = {

+		Model,

+		paginate

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'test-definitions', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'test-definitions');

+

+	service.hooks(hooks);

+};

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
index 0000000..08a55ee
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.class.js
@@ -0,0 +1,109 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const axios = require('axios');

+const { parseString, Builder } = require('xml2js');

+const pickleRick = require('pickle-rick');

+const Response = require('http-response-object');

+const request = require('request');

+const util = require('../../../lib/otf-util');

+

+/* eslint-disable no-unused-vars */

+class Service {

+	constructor (options) {

+		this.options = options || {};

+	}

+

+	async find (params) {

+

+	}

+

+	// Check process definition key to see if unique

+	async get (id, params) {

+		// return await axios.get(this.options.app.get('camundaApi').url + 'otf/tcu/process-instance-completion-check/v1/' + id,

+		// 	{

+		// 		headers: {

+		// 			Authorization: 'Basic ' +

+		// 		util.base64Encode(

+		// 			this.options.app.get('serviceApi').aafId + ':' +

+		// 			this.options.app.get('serviceApi').aafPassword)

+		// 		}

+		// 	})

+		// 	.then(result => {

+		// 		return new Response(200, {}, result.data);

+		// 	})

+		// 	.catch(err => {

+		// 		console.log(err);

+		// 	});

+		

+		let options = {

+			url: this.options.app.get('camundaApi').url + 'otf/tcu/process-instance-completion-check/v1/' + id,

+			headers: {

+				'Authorization': 'Basic ' + util.base64Encode(this.options.app.get('serviceApi').aafId + ':' + this.options.app.get('serviceApi').aafPassword),

+				'Content-Type': "application/json"

+			},

+			rejectUnauthorized: false

+		}

+		

+		return await new Promise((resolve, reject) => {

+			request.get(options, (err, res, body) => {

+				if(err){

+					reject(err);

+				}

+				if(res && res.body){

+					res.body = JSON.parse(res.body);

+				}

+				resolve(res);

+			});

+		}).then(

+			res => {

+				return res;

+			}

+		).catch(

+			err => {

+				console.log(err);

+			}

+		);

+	}

+

+	async create (data, params) {

+		if (Array.isArray(data)) {

+			return Promise.all(data.map(current => this.create(current, params)));

+		}

+	}

+

+	async update (id, data, params) {

+		return data;

+	}

+

+	async patch (id, data, params) {

+		return data;

+	}

+

+	async remove (id, params) {

+		return { id };

+	}

+

+	async parseAndUpload (data, params, method) {

+

+	}

+}

+

+module.exports = function (options) {

+	return new Service(options);

+};

+

+module.exports.Service = Service;

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
index 0000000..98dc42e
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.hooks.js
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt')],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	after: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..4c022f4
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-execution-status/test-execution-status.service.js
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `bpmnValidate` service on path `/test-definition/image`

+const createService = require('./test-execution-status.class.js');

+const hooks = require('./test-execution-status.hooks');

+

+module.exports = function (app) {

+	const paginate = app.get('paginate');

+

+	const options = {

+		paginate,

+		app

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'test-execution-status', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'test-execution-status');

+

+	service.hooks(hooks);

+};

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
index 0000000..5e43d29
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-executions/test-executions.hooks.js
@@ -0,0 +1,56 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { permissions, limitFields } = require('../../hooks/permissions/permissions.js');

+const { iff } = require('feathers-hooks-common');

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt'), permissions('testExecutions')],

+		find: [],

+		get: [],

+		create: [

+			function (context) {

+				context.data.executorId = context.params.user._id;

+				return context;

+			}

+		],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	after: {

+		all: [iff(context => context.params.provider, limitFields())],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..30fd7c4
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-executions/test-executions.service.js
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `testDefinition` service on path `/test-definition`

+const createService = require('feathers-mongoose');

+const createModel = require('../../models/test-executions.model');

+const hooks = require('./test-executions.hooks');

+

+module.exports = function (app) {

+	const Model = createModel(app);

+	const paginate = app.get('paginate');

+

+	const options = {

+		Model,

+		paginate

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'test-executions', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'test-executions');

+

+	service.hooks(hooks);

+};

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
index 0000000..de285e6
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-heads/test-heads.hooks.js
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { permissions, limitFields } = require('../../hooks/permissions/permissions.js');

+const { iff } = require('feathers-hooks-common');

+const convertToJSON = require('../../hooks/convertToJSON.js');

+const convertToYAML = require('../../hooks/convertToYAML.js');

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt'), permissions('testHeads')],

+		find: [],

+		get: [],

+		create: [convertToJSON(), function (context) {

+			context.data.creatorId = context.params.user._id;

+			return context;

+		}

+		],

+		update: [convertToJSON()],

+		patch: [convertToJSON()],

+		remove: [

+		]

+	},

+

+	after: {

+		all: [iff(context => context.params.provider, limitFields())],

+		find: [convertToYAML()],

+		get: [convertToYAML()],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..9b3b167
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-heads/test-heads.service.js
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `testHeads` service on path `/test-heads`

+const createService = require('feathers-mongoose');

+const createModel = require('../../models/test-heads.model');

+const hooks = require('./test-heads.hooks');

+

+module.exports = function (app) {

+	const Model = createModel(app);

+	const paginate = app.get('paginate');

+

+	const options = {

+		Model,

+		paginate

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'test-heads', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'test-heads');

+

+	service.hooks(hooks);

+};

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
index 0000000..ade8335
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-instances/test-instances.hooks.js
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const { iff } = require('feathers-hooks-common');

+const { permissions, limitFields } = require('../../hooks/permissions/permissions');

+const convertToJSON = require('../../hooks/convertToJSON.js');

+const convertToYAML = require('../../hooks/convertToYAML.js');

+const testDefinitionIsDeployed = require('../../hooks/testDefinitionIsDeployed.js');

+const {skipRemainingHooks} = require('feathers-hooks-common');

+

+

+module.exports = {

+	before: {

+		all: [authenticate('jwt'), permissions('testInstances')],

+		find: [],

+		get: [],

+		create: [iff(testDefinitionIsDeployed(), convertToJSON()).else(function(context) {

+			

+			throw new Error('Test Definition Must have a deployed BPMN.');

+			

+		  }

+	 )],

+		update: [convertToJSON()],

+		patch: [],

+		remove: [

+		]

+	},

+

+	after: {

+		all: [],

+		find: [convertToYAML()],

+		get: [ convertToYAML()],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..bb74ef6
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/test-instances/test-instances.service.js
@@ -0,0 +1,38 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `testInstances` service on path `/test-instances`

+const createService = require('feathers-mongoose');

+const createModel = require('../../models/test-instances.model');

+const hooks = require('./test-instances.hooks');

+

+module.exports = function (app) {

+	const Model = createModel(app);

+	const paginate = app.get('paginate');

+

+	const options = {

+		Model,

+		paginate

+	};

+

+	// Initialize our service with any options it requires

+	app.use(app.get('base-path') + 'test-instances', createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'test-instances');

+

+	service.hooks(hooks);

+};

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
index 0000000..d20655b
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/users/users.hooks.js
@@ -0,0 +1,181 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { authenticate } = require('@feathersjs/authentication').hooks;

+const filter = require('../../hooks/filters.js');

+const checkPermissions = require('feathers-permissions');

+const authManagement = require('../auth-management/notifier');

+const errors = require('@feathersjs/errors');

+

+const {

+	hashPassword, protect

+} = require('@feathersjs/authentication-local').hooks;

+const { iff, disallow } = require('feathers-hooks-common'); 

+const verifyHooks = require('feathers-authentication-management').hooks;

+const skip = require('@feathersjs/feathers').SKIP;

+

+const { permissions, limitFields } = require('../../hooks/permissions/permissions');

+

+module.exports = {

+	before: {

+		all: [],

+		find: [

+			authenticate('jwt'),

+			permissions('users'),

+			function(context){

+				if(!context.params.user){

+					return skip;

+				}

+			}

+		],

+		get: [

+			authenticate('jwt'),

+			permissions('users'),

+			function(context){

+				if(!context.params.user){

+					return skip;

+				}

+			}

+		],

+		create: [hashPassword(),

+				function(context){

+					return verifyHooks.addVerification(context.app.get('base-path') + 'authManagement')(context);

+				},

+				function (context) {

+					context.data.enabled = false;

+					// await context.app.services[context.app.get('base-path') + 'groups']

+					// .find({

+					// 	query : {

+					// 		groupName: "Public"

+					// 	}

+					// })

+					// .then( result => {	

+					// 	if(result){

+					// 		await context.app.services[context.app.get('base-path') + 'groups']

+					// 		.patch({

+					// 			_id : result._id,

+                    //     		$push: { members: { userId : user._id, roles: ["user"]}}

+					// 		});

+					// 	}

+					// });

+					context.data.groups = [

+						{

+							groupId: '5bdb2bdbd6b0d1f97953fbd7',

+							permissions: [

+								'admin'

+							]

+						}

+					];

+

+				}

+		],

+		update: [

+			hashPassword(),

+			authenticate('jwt'),

+			permissions('users')

+		],

+		patch:

+			[

+

+				hashPassword(),

+				authenticate('jwt'),

+				iff(context => context.params.provider === undefined).else(

+					permissions('users'),

+					async function(context){

+						if(context.data.enabled){

+							 await this.get(context.id)

+								.then(function(user) {

+									if(!user.enabled){

+										context.sendEmail = true;

+

+									}

+								});

+						}

+					}

+				)

+			// commonHooks

+			// 	.iff(checkPermissions({

+			// 		roles: [ 'admin' ]

+			// 	}))

+			// 	.else(commonHooks.iff(

+			// 		commonHooks.isProvider('external'),

+			// 		commonHooks.preventChanges(

+			// 			'email',

+			// 			'isVerified',

+			// 			'verifyToken',

+			// 			'verifyShortToken',

+			// 			'verifyExpires',

+			// 			'verifyChanges',

+			// 			'resetToken',

+			// 			'resetShortToken',

+			// 			'resetExpires'

+			// 		)

+			// 	))

+		],

+		remove: [

+			authenticate('jwt'),

+			permissions('users')

+		]

+	},

+

+	after: {

+		all: [

+			// Make sure the password field is never sent to the client

+			// Always must be the last hook

+			protect('password'),

+		],

+		find: [iff(context => context.params.provider === undefined).else(limitFields())],

+		get: [iff(context => context.params.provider === undefined).else(limitFields())],

+		create: [

+			context => {

+				authManagement(context.app).notifier('resendVerifySignup', context.result);

+			},

+			function (context) {

+				

+				// await context.app.services[context.app.get('base-path') + 'groups']

+				// .get(context.data.parentGroupId, context.params)

+				// .then( result => {	

+				// 	group = result;

+				// });

+			},

+			verifyHooks.removeVerification()

+		],

+		update: [iff(context => context.params.provider === undefined).else(limitFields())],

+		patch: [iff(context => context.params.provider === undefined).else(limitFields()),

+			context => {

+				let data = context['data']

+				if(data && context.sendEmail){

+					let enabled = data['enabled'];

+						if(enabled){

+							authManagement(context.app).notifier('sendApprovalNotification', context.result)

+

+						}

+				}

+			}

+		],

+		remove: [iff(context => context.params.provider === undefined).else(limitFields())]

+	},

+

+	error: {

+		all: [],

+		find: [],

+		get: [],

+		create: [],

+		update: [],

+		patch: [],

+		remove: []

+	}

+};

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
index 0000000..b4350bb
--- /dev/null
+++ b/otf-frontend/server/src/feathers/services/users/users.service.js
@@ -0,0 +1,62 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+// Initializes the `users` service on path `/users`

+const createService = require('feathers-mongoose');

+const createModel = require('../../models/users.model');

+const hooks = require('./users.hooks');

+

+const RateLimit = require('express-rate-limit');

+const MongoStore = require('rate-limit-mongo');

+

+module.exports = function (app) {

+	const Model = createModel(app);

+	const paginate = app.get('paginate');

+

+	const options = {

+		Model,

+		paginate

+	};

+

+	const mongoConfig = app.get('mongo');

+	const rateLimitConfig = app.get('rate-limit');

+

+	const createUserLimiter = new RateLimit({

+		store: new MongoStore({

+			uri: 'mongodb://' + mongoConfig.username + ':' + mongoConfig.password + '@' + mongoConfig.baseUrl +

+				mongoConfig.dbOtf + '?replicaSet=' + mongoConfig.replicaSet,

+			collectionName: rateLimitConfig.mongoStore.collection

+		}),

+		max: app.get('rate-limit').services.users.max,

+		windowsMs: app.get('rate-limit').services.users.windowMs,

+		message: app.get('rate-limit').services.users.message

+	});

+

+	// Initialize our service with any options it requires,

+	// and limit any POST methods.

+	app.use(app.get('base-path') + 'users', (req, res, next) => {

+		if (req.method === 'POST') {

+			createUserLimiter(req, res, next);

+		} else {

+			next();

+		}

+	}, createService(options));

+

+	// Get our initialized service so that we can register hooks

+	const service = app.service(app.get('base-path') + 'users');

+

+	service.hooks(hooks);

+};

diff --git a/otf-frontend/server/src/lib/logger.js b/otf-frontend/server/src/lib/logger.js
new file mode 100644
index 0000000..4927948
--- /dev/null
+++ b/otf-frontend/server/src/lib/logger.js
@@ -0,0 +1,71 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const { createLogger, format, transports, addColors } = require('winston');

+const { combine, timestamp, label, simple, colorize, splat, printf } = format;

+const cluster = require('cluster');

+

+const getLabel = function () {

+	if (cluster.isMaster) return 'OTF-master';

+	return 'OTF-worker-' + cluster.worker.id;

+};

+

+const config = {

+	levels: {

+		error: 0,

+		debug: 1,

+		warn: 2,

+		data: 3,

+		info: 4,

+		verbose: 5,

+		silly: 6,

+		custom: 7

+	},

+	colors: {

+		error: 'red',

+		debug: 'blue',

+		warn: 'yellow',

+		data: 'grey',

+		info: 'green',

+		verbose: 'cyan',

+		silly: 'magenta',

+		custom: 'yellow'

+	}

+};

+

+addColors(config.colors);

+

+const logFormat = printf(info => {

+	return `${info.timestamp} [${info.label}] ${info.level}: ${info.message}`;

+});

+

+const logger = module.exports = createLogger({

+	levels: config.levels,

+	format: combine(

+		label({ label: getLabel() }),

+		timestamp(),

+		splat(),

+		colorize(),

+		simple(),

+		logFormat

+	),

+	transports: [

+		new transports.Console()

+	],

+	level: 'custom'

+});

+

+module.exports = logger;

diff --git a/otf-frontend/server/src/lib/mongoose.js b/otf-frontend/server/src/lib/mongoose.js
new file mode 100644
index 0000000..a8d5b9d
--- /dev/null
+++ b/otf-frontend/server/src/lib/mongoose.js
@@ -0,0 +1,30 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const mongoose = require('mongoose');

+

+module.exports = function (app) {

+	const mongoData = app.get('mongo');

+	const connectionString = 'mongodb://' + mongoData.username + ':' + mongoData.password + '@' + mongoData.baseUrl + mongoData.dbOtf + '?replicaSet=' + mongoData.replicaSet;

+

+	mongoose.connect(connectionString, { useNewUrlParser: true }).then(null, error => {

+		console.log('caught', error.message);

+	});

+	mongoose.Promise = global.Promise;

+	mongoose.set('useCreateIndex', true);

+

+	app.set('mongooseClient', mongoose);

+};

diff --git a/otf-frontend/server/src/lib/music.js b/otf-frontend/server/src/lib/music.js
new file mode 100644
index 0000000..aa39cdc
--- /dev/null
+++ b/otf-frontend/server/src/lib/music.js
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const config = require('../config/default.json');

+const btoa = require('btoa');

+

+module.exports.parser = function (response) {

+	var jsonObject = {

+		data: [],

+		status: ''

+	};

+

+	if (response.result) {

+		// loop through musics rusults and make it into an array

+		for (var key in response.result) {

+			if (response.result.hasOwnProperty(key)) {

+				jsonObject.data.push(response.result[key]);

+			}

+		}

+	}

+

+	// set store status in new reponse

+	jsonObject.status = response.status;

+

+	return jsonObject;

+};

+

+module.exports.stringifyParams = function (params) {

+	var string = '';

+	var count = 0;

+

+	for (var key in params.query) {

+		if (params.query.hasOwnProperty(key) && key[0] != '$') {

+			if (count > 0) {

+				string += '&&';

+			}

+			string += key + '=' + params.query[key];

+			count++;

+		}

+	}

+

+	return string;

+};

+

+module.exports.musicHeaders = {

+	'Content-Type': 'application/json',

+	'ns': config.music.ns,

+	'X-minorVersion': config.music['X-minorVersion'],

+	'X-patchVersion': config.music['X-patchVersion'],

+	'Authorization': 'Basic ' + btoa(config.music.username + ':' + config.music.password)

+};

+

+module.exports.musicUrl = config.music.url;

diff --git a/otf-frontend/server/src/lib/otf-util.js b/otf-frontend/server/src/lib/otf-util.js
new file mode 100644
index 0000000..359a4d1
--- /dev/null
+++ b/otf-frontend/server/src/lib/otf-util.js
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+const ObjectId = require('mongoose').Types.ObjectId;

+

+module.exports = {

+	base64Encode: function (value) {

+		return Buffer.from(value).toString('base64');

+	},

+	isValidObjectId: function (value) {

+		const generatedObjectId = new ObjectId(value);

+

+		return generatedObjectId.toString() === value;

+	},

+	isJsonString: function (str) {

+		try {

+			JSON.parse(str);

+		} catch (e) {

+			return false;

+		}

+		return true;

+	}

+};

diff --git a/otf-frontend/server/test/app.test.js b/otf-frontend/server/test/app.test.js
new file mode 100644
index 0000000..67338c3
--- /dev/null
+++ b/otf-frontend/server/test/app.test.js
@@ -0,0 +1,55 @@
+const assert = require('assert');

+const rp = require('request-promise');

+const url = require('url');

+const app = require('../src/app');

+

+const port = app.get('port') || 3030;

+const getUrl = pathname => url.format({

+	hostname: app.get('host') || 'localhost',

+	protocol: 'http',

+	port,

+	pathname

+});

+

+describe('Feathers application tests', () => {

+	before(function (done) {

+		this.server = app.listen(port);

+		this.server.once('listening', () => done());

+	});

+

+	after(function (done) {

+		this.server.close(done);

+	});

+

+	it('starts and shows the index page', () => {

+		return rp(getUrl()).then(body =>

+			assert.ok(body.indexOf('<html>') !== -1)

+		);

+	});

+

+	describe('404', function () {

+		it('shows a 404 HTML page', () => {

+			return rp({

+				url: getUrl('path/to/nowhere'),

+				headers: {

+					'Accept': 'text/html'

+				}

+			}).catch(res => {

+				assert.strictEqual(res.statusCode, 404);

+				assert.ok(res.error.indexOf('<html>') !== -1);

+			});

+		});

+

+		it('shows a 404 JSON error without stack trace', () => {

+			return rp({

+				url: getUrl('path/to/nowhere'),

+				json: true

+			}).catch(res => {

+				assert.strictEqual(res.statusCode, 404);

+				assert.strictEqual(res.error.code, 404);

+				assert.strictEqual(res.error.message, 'Page not found');

+				assert.strictEqual(res.error.name, 'NotFound');

+			});

+		});

+	});

+});

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
index 0000000..71d02c6
--- /dev/null
+++ b/otf-frontend/server/test/hooks/group-filter.test.js
@@ -0,0 +1,25 @@
+const assert = require('assert');

+const feathers = require('@feathersjs/feathers');

+const groupFilter = require('../../src/hooks/group-filter');

+

+describe('\'groupFilter\' hook', () => {

+	let app;

+

+	beforeEach(() => {

+		app = feathers();

+

+		app.use('/dummy', {

+			async get (id) {

+				return { id };

+			}

+		});

+

+		app.service('dummy').hooks({});

+	});

+

+	it('runs the hook', async () => {

+		const result = await app.service('dummy').get('test');

+

+		assert.deepEqual(result, { id: 'test' });

+	});

+});

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
index 0000000..23d1ff0
--- /dev/null
+++ b/otf-frontend/server/test/services/bpmn-upload.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'bpmnUpload\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('bpmn-upload');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

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
index 0000000..fb00767
--- /dev/null
+++ b/otf-frontend/server/test/services/bpmn-validate.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'bpmnValidate\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('bpmn-validate');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

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
index 0000000..6e46510
--- /dev/null
+++ b/otf-frontend/server/test/services/groups-m.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'groupsM\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('groups-m');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

diff --git a/otf-frontend/server/test/services/groups.test.js b/otf-frontend/server/test/services/groups.test.js
new file mode 100644
index 0000000..c66c9c7
--- /dev/null
+++ b/otf-frontend/server/test/services/groups.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'groups\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('groups');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

diff --git a/otf-frontend/server/test/services/health.test.js b/otf-frontend/server/test/services/health.test.js
new file mode 100644
index 0000000..212f034
--- /dev/null
+++ b/otf-frontend/server/test/services/health.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'health\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('health');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

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
index 0000000..034e933
--- /dev/null
+++ b/otf-frontend/server/test/services/strategy-upload.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'strategyUpload\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('strategy-upload');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

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
index 0000000..556d710
--- /dev/null
+++ b/otf-frontend/server/test/services/test-definition.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'testDefinition\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('test-definition');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

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
index 0000000..44fa679
--- /dev/null
+++ b/otf-frontend/server/test/services/test-heads.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'testHeads\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('test-heads');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

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
index 0000000..2db66b9
--- /dev/null
+++ b/otf-frontend/server/test/services/test-instances.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'testInstances\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('test-instances');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

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
index 0000000..51304e5
--- /dev/null
+++ b/otf-frontend/server/test/services/test-requests.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'testRequests\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('test-requests');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

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
index 0000000..35bf0d5
--- /dev/null
+++ b/otf-frontend/server/test/services/test-strategies.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'testStrategies\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('test-strategies');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

diff --git a/otf-frontend/server/test/services/tests.test.js b/otf-frontend/server/test/services/tests.test.js
new file mode 100644
index 0000000..0d03d13
--- /dev/null
+++ b/otf-frontend/server/test/services/tests.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'tests\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('tests');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

diff --git a/otf-frontend/server/test/services/users.test.js b/otf-frontend/server/test/services/users.test.js
new file mode 100644
index 0000000..bed24e0
--- /dev/null
+++ b/otf-frontend/server/test/services/users.test.js
@@ -0,0 +1,10 @@
+const assert = require('assert');

+const app = require('../../src/app');

+

+describe('\'users\' service', () => {

+	it('registered the service', () => {

+		const service = app.service('users');

+

+		assert.ok(service, 'Registered the service');

+	});

+});

diff --git a/otf-ping-test-head/Dockerfile b/otf-ping-test-head/Dockerfile
index 0ebb8f8..182eaa4 100644
--- a/otf-ping-test-head/Dockerfile
+++ b/otf-ping-test-head/Dockerfile
@@ -1,9 +1,14 @@
 FROM python:2.7

 

-ARG HTTP_PROXY="localhost:8080"

-ARG HTTPS_PROXY="localhost:8080"

-ARG http_proxy="localhost:8080"

-ARG https_proxy="localhost:8080"

+# ARG HTTP_PROXY="localhost:8080"

+# ARG HTTPS_PROXY="localhost:8080"

+# ARG http_proxy="localhost:8080"

+# ARG https_proxy="localhost:8080"

+

+

+ENV NAMESPACE=namespace

+ENV APP_NAME=otf-ping-test-head

+ENV APP_VERSION=1.0

 

 RUN python --version

 

diff --git a/otf-robot-test-head/Dockerfile b/otf-robot-test-head/Dockerfile
index 7358b21..49267ac 100644
--- a/otf-robot-test-head/Dockerfile
+++ b/otf-robot-test-head/Dockerfile
@@ -1,9 +1,18 @@
 FROM python:2.7

 

-ARG HTTP_PROXY="http://localhost:8080" 

-ARG HTTPS_PROXY="http://localhost:8080" 

-ARG http_proxy="http://localhost:8080" 

-ARG https_proxy="http://localhost:8080"

+# ARG HTTP_PROXY="http://localhost:8080" 

+# ARG HTTPS_PROXY="http://localhost:8080" 

+# ARG http_proxy="http://localhost:8080" 

+# ARG https_proxy="http://localhost:8080"

+

+ENV NAMESPACE=namespace

+ENV APP_NAME=otf-robot-test-head

+ENV APP_VERSION=1.0

+ENV OTF_MONGO_HOSTS=localhost:27017

+ENV OTF_MONGO_DATABASE=otf

+ENV OTF_MONGO_REPLICASET=mongoOTF

+ENV OTF_MONGO_USERNAME=username

+ENV OTF_MONGO_PASSWORD=password

 

 RUN python --version

 

diff --git a/otf-service-api/.gitignore b/otf-service-api/.gitignore
new file mode 100644
index 0000000..681073c
--- /dev/null
+++ b/otf-service-api/.gitignore
@@ -0,0 +1,34 @@
+/target/

+tokens/

+out/

+src/main/resources/otf_dev.p12

+/otf/

+

+

+*.log

+

+!.mvn/wrapper/maven-wrapper.jar

+

+### STS ###

+.apt_generated

+.classpath

+.factorypath

+.project

+.settings

+.springBeans

+.sts4-cache

+

+### IntelliJ IDEA ###

+.idea

+*.iws

+*.iml

+*.ipr

+/null/

+

+### NetBeans ###

+/nbproject/private/

+/build/

+/nbbuild/

+/dist/

+/nbdist/

+/.nb-gradle/

diff --git a/otf-service-api/Jenkinsfile b/otf-service-api/Jenkinsfile
new file mode 100644
index 0000000..68e8d66
--- /dev/null
+++ b/otf-service-api/Jenkinsfile
@@ -0,0 +1,169 @@
+#!/usr/bin/env groovy

+

+properties([[$class: 'ParametersDefinitionProperty', parameterDefinitions: [

+        [$class: 'hudson.model.StringParameterDefinition', name: 'PHASE', defaultValue: "BUILD"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'ENV', defaultValue: "dev"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'MECHID', defaultValue: "username"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'KUBE_CONFIG', defaultValue: "kubeConfig-dev"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'OTF_MONGO_DB', defaultValue: "otf_mongo_dev_db"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'OTF_CAMUNDA_DB', defaultValue: "otf_camunda_dev_db"],

+        [$class: 'hudson.model.StringParameterDefinition', name: 'TILLER_NAMESPACE', defaultValue: "org-oran-otf"]

+        

+]]])

+

+echo "Build branch: ${env.BRANCH_NAME}"

+

+node("docker") {

+    stage 'Checkout'

+    checkout scm

+    PHASES = PHASE.tokenize('_')

+    echo "PHASES : " + PHASES

+    pom = readMavenPom file: 'pom.xml'

+    ARTIFACT_ID = pom.artifactId

+    VERSION = pom.version

+    LABEL_VERSION = pom.version.replaceAll("\\.", "-")

+    echo "LabelVerion: " + LABEL_VERSION

+    NAMESPACE = pom.groupId

+    echo "Tiller Namespace: " + TILLER_NAMESPACE

+    DOCKER_REGISTRY = pom.properties['docker.registry']

+

+

+

+	if( ENV.equalsIgnoreCase("dev") ){

+	    IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE  + "/" + ARTIFACT_ID + ":" + VERSION

+	

+	}

+	if( ENV.equalsIgnoreCase("prod") || ENV.equalsIgnoreCase("prod-dr")){

+	    IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE + ".prod" + "/" + ARTIFACT_ID + ":" + VERSION

+	

+	}    

+    if( ENV.equalsIgnoreCase("st") ){

+        IMAGE_NAME = pom.properties['docker.registry'] + "/" + NAMESPACE + ".st" + "/" + ARTIFACT_ID + ":" + VERSION

+    

+    } 

+	

+	echo "Artifact: " + IMAGE_NAME

+

+    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}"]) {

+

+        echo "JAVA_HOME=${env.JAVA_HOME}"

+        echo "MAVEN_HOME=${env.MAVEN_HOME}"

+        echo "PATH=${env.PATH}"

+        echo "HELM_HOME=${env.HELM_HOME}"

+

+        wrap([$class: 'ConfigFileBuildWrapper', managedFiles: [

+                [fileId: 'maven-settings.xml', variable: 'MAVEN_SETTINGS']

+        ]]) {

+

+

+            if (PHASES.contains("BUILD")) {

+                stage 'Compile'

+                sh 'mvn -s $MAVEN_SETTINGS clean compile'

+

+                //stage 'Unit Test'

+                sh 'mvn -s $MAVEN_SETTINGS test -DskipTests'

+

+                stage 'Package'

+                sh 'mvn -s $MAVEN_SETTINGS package -DskipTests'

+//                sh 'mvn -DskipTests -Dmaven.test.skip=true -s $MAVEN_SETTINGS package'

+

+//                stage 'Verify'

+                sh 'mvn -s $MAVEN_SETTINGS verify -DskipTests'

+

+                stage 'Publish Artifact'

+

+                withCredentials([usernamePassword(credentialsId: MECHID, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {

+

+                    echo "Artifact: " + IMAGE_NAME

+

+                    sh """

+						docker login $DOCKER_REGISTRY --username $USERNAME --password $PASSWORD

+						docker build -t $IMAGE_NAME -f target/Dockerfile target

+						docker push $IMAGE_NAME

+					"""

+                }

+

+            }

+            if (PHASES.contains("DEPLOY") || PHASES.contains("UNDEPLOY")) {

+

+                stage 'Init Helm'

+

+                //check if helm exists if not install

+                if (fileExists('linux-amd64/helm')) {

+                    sh """

+						echo "helm is already installed"

+					"""

+                } else {

+                    //download helm

+                    sh """

+						echo "installing helm"

+						wget  https://storage.googleapis.com/kubernetes-helm/helm-v2.8.2-linux-amd64.tar.gz

+						tar -xf helm-v2.8.2-linux-amd64.tar.gz

+						rm helm-v2.8.2-linux-amd64.tar.gz

+					"""

+                }

+

+                withCredentials([file(credentialsId: KUBE_CONFIG, variable: 'KUBECONFIG')]) {

+

+                    dir('helm') {

+                        //check if charts are valid, and then perform dry run, if successful then upgrade/install charts

+

+                        if (PHASES.contains("UNDEPLOY")) {

+                            stage 'Undeploy'

+

+                            sh """

+      							helm delete --tiller-namespace=$TILLER_NAMESPACE --purge $ARTIFACT_ID

+      						"""

+                        }

+

+                        //NOTE Double quotes are used below to access groovy variables like artifact_id and tiller_namespace

+                        if (PHASES.contains("DEPLOY")) {

+                            stage 'Deploy'

+                            withCredentials([

+                                    usernamePassword(credentialsId: OTF_MONGO_DB, usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD'),

+                            ]) {

+                                sh """										

+								    echo "Validate Yaml"

+                                    helm lint $ARTIFACT_ID

+

+                                    echo "View Helm Templates"

+                                    helm template $ARTIFACT_ID \

+                                    	--set appName=$ARTIFACT_ID \

+                                    	--set version=$VERSION \

+                                    	--set image=$IMAGE_NAME\

+                                    	--set env=$ENV \

+                                    	--set otf.mongo.username=$USERNAME \

+                                    	--set otf.mongo.password=$PASSWORD \

+                                    	--set namespace=$TILLER_NAMESPACE

+                                    	

+

+                                    echo "Perform Dry Run Of Install"

+                                    helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install --dry-run $ARTIFACT_ID $ARTIFACT_ID \

+                                    	--set appName=$ARTIFACT_ID \

+                                    	--set version=$VERSION \

+                                    	--set image=$IMAGE_NAME\

+                                    	--set env=$ENV \

+                                    	--set otf.mongo.username=$USERNAME \

+                                    	--set otf.mongo.password=$PASSWORD \

+                                    	--set namespace=$TILLER_NAMESPACE

+

+                                    echo "Helm Install/Upgrade"

+                                    helm upgrade --tiller-namespace=$TILLER_NAMESPACE --install $ARTIFACT_ID $ARTIFACT_ID \

+                                    	--set appName=$ARTIFACT_ID \

+                                    	--set version=$VERSION \

+                                    	--set image=$IMAGE_NAME\

+                                    	--set env=$ENV \

+                                    	--set otf.mongo.username=$USERNAME \

+                                    	--set otf.mongo.password=$PASSWORD \

+                                    	--set namespace=$TILLER_NAMESPACE

+                                    

+								"""

+                            }

+                        }

+

+                    }

+                }

+            }

+        }

+    }

+}
\ No newline at end of file
diff --git a/otf-service-api/LICENSE.txt b/otf-service-api/LICENSE.txt
new file mode 100644
index 0000000..695ac56
--- /dev/null
+++ b/otf-service-api/LICENSE.txt
@@ -0,0 +1,28 @@
+Unless otherwise specified, all software contained herein is licensed

+under the Apache License, Version 2.0 (the "Software License");

+you may not use this software except in compliance with the Software

+License. You may obtain a copy of the Software License at

+

+http://www.apache.org/licenses/LICENSE-2.0

+

+Unless required by applicable law or agreed to in writing, software

+distributed under the Software License is distributed on an "AS IS" BASIS,

+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+See the Software License for the specific language governing permissions

+and limitations under the Software License.

+

+

+

+Unless otherwise specified, all documentation contained herein is licensed

+under the Creative Commons License, Attribution 4.0 Intl. (the

+"Documentation License"); you may not use this documentation except in

+compliance with the Documentation License. You may obtain a copy of the

+Documentation License at

+

+https://creativecommons.org/licenses/by/4.0/

+

+Unless required by applicable law or agreed to in writing, documentation

+distributed under the Documentation License is distributed on an "AS IS"

+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or

+implied. See the Documentation License for the specific language governing

+permissions and limitations under the Documentation License.

diff --git a/otf-service-api/README.txt b/otf-service-api/README.txt
new file mode 100644
index 0000000..b6e4e99
--- /dev/null
+++ b/otf-service-api/README.txt
@@ -0,0 +1,13 @@
+You must setup environment variables to run and test locally

+These environment variables will be secretes when deployed on kubernetes.

+	AAF_ID (mechid for cadi aaf)

+	AAF_PASSWORD (password for mechid)

+	CADI_KEYFILE (cadi keyfile location for aaf)

+		

+		Generate AAF_PASSWORD and CADI_KEYFILE:

+			java -jar cadi-core-1.4.2.jar keygen keyfile

+			java -jar cadi-core-1.4.2.jar digest AAF_MECHID_PASSWORD keyfile  > digest.txt 2>&1

+	

+	AAF_PERM_TYPE (permission type to check for when authorization a user)

+	

+	
\ No newline at end of file
diff --git a/otf-service-api/docker/Dockerfile b/otf-service-api/docker/Dockerfile
new file mode 100644
index 0000000..e0e9e53
--- /dev/null
+++ b/otf-service-api/docker/Dockerfile
@@ -0,0 +1,43 @@
+FROM openjdk:8

+

+ENV NAMESPACE=namespace

+ENV APP_NAME=otf-service-api

+ENV AAF_PERM_TYPE=type

+ENV AAF_ID=username

+ENV AAF_MECH_PASSWORD=password

+ENV AAF_PASSWORD=password

+ENV CADI_KEYFILE=/opt/secret/keyfile

+ENV CADI_HOSTNAME=localhost

+ENV APP_VERSION=1.0

+ENV OTF_MONGO_HOSTS=localhost:27017

+ENV OTF_MONGO_USERNAME=username

+ENV OTF_MONGO_PASSWORD=password

+ENV OTF_MONGO_REPLICASET=mongoOTF

+ENV OTF_MONGO_DATABASE=otf

+ENV otf.camunda.host=https://localhost

+ENV otf.camunda.port=31313

+ENV otf.camunda.executionUri=otf/tcu/execute-test/v1

+ENV otf.camunda.pollingUri=otf/tcu/process-instance-completion-check/v1

+ENV otf.camunda.deploymentUri=otf/tcu/deploy-test-strategy-zip/v1

+ENV otf.camunda.processDefinitionKeyUri=rest/process-definition/key

+ENV otf.camunda.deploymentDeletionUri=otf/tcu/delete-test-strategy/v1/deployment-id

+ENV otf.camunda.testDefinitionDeletionUri=otf/tcu/delete-test-strategy/v1/test-definition-id

+ENV otf.camunda.uri.execute-test=otf/tcu/execute/workflowRequest

+ENV otf.camunda.uri.process-instance-completion-check=otf/tcu/process-instance-completion-check/v1

+ENV otf.camunda.uri.deploy-test-strategy-zip=otf/tcu/deploy-test-strategy-zip/v1

+ENV otf.camunda.uri.process-definition=rest/process-definition/key

+ENV otf.camunda.uri.delete-test-strategy=otf/tcu/delete-test-strategy/v1/deployment-id

+ENV otf.camunda.uri.delete-test-strategy-test-definition-id=otf/tcu/delete-test-strategy/v1/test-definition-id

+ENV otf.camunda.uri.health=/otf/health/v1

+ENV otf.api.poll-interval=6000

+ENV otf.api.poll-attempts=50

+ENV OTF_CERT_PATH=opt/cert/cert.p12

+ENV OTF_CERT_PASS=password

+

+COPY otf-service-api.jar app.jar

+

+RUN mkdir -p /otf/logs

+

+ADD src src

+

+ENTRYPOINT ["java", "-jar", "app.jar"]

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
index 0000000..7c05894
--- /dev/null
+++ b/otf-service-api/helm/otf-service-api/Chart.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1

+appVersion: "1.0"

+description: A Helm chart the OTF TCU Service API

+name: otf-service-api

+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
index 0000000..3a406d3
--- /dev/null
+++ b/otf-service-api/helm/otf-service-api/templates/deployment.yaml
@@ -0,0 +1,280 @@
+apiVersion: extensions/v1beta1

+kind: Deployment

+metadata:

+  name: {{ .Values.appName}}

+  namespace: {{.Values.namespace}}

+  labels:

+    app: {{ .Values.appName}}

+    version: {{.Values.version}}

+spec:

+  {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+  replicas: {{ .Values.replicas.prod}}

+  {{ else if  eq .Values.env "st"}}

+  replicas: {{ .Values.replicas.st}}

+  {{ else }}

+  replicas: {{ .Values.replicas.dev}}

+  {{ end }}

+  selector:

+    matchLabels:

+      app: {{ .Values.appName}}

+      version: {{.Values.version}}

+  template:

+    metadata:

+      labels:

+        app: {{ .Values.appName}}

+        version: {{.Values.version}}

+    spec:

+      revisionHistoryLimit: 1   # keep one replica set to allow rollback

+      minReadySeconds: 10

+      strategy:

+        # indicate which strategy we want for rolling update

+        type: RollingUpdate

+        rollingUpdate:

+          maxSurge: 1

+          maxUnavailable: 1

+      serviceAccount: default

+      volumes:

+      - name: {{ .Values.appName}}-aaf-volume

+        secret:

+          secretName: {{.Values.sharedSecret}}

+      - name: {{ .Values.appName}}-keyfile-volume

+        secret:

+          secretName: {{.Values.sharedSecret}}

+          optional: true

+          items:

+          - key: cadi_keyfile

+            path: keyfile

+      - name: {{ .Values.appName}}-cert-volume

+        secret:

+          secretName: {{.Values.sharedCert}}

+          optional: true

+          items:

+          - key: PKCS12_CERT

+            {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+            path: {{ .Values.cert.prod.name | quote }}

+            {{ else if eq  .Values.env "st" }}

+            path: {{ .Values.cert.st.name | quote }}

+            {{ else }}

+            path: {{ .Values.cert.dev.name | quote }}

+            {{ end }}          

+      {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}}

+      {{else}}

+      - name: logging-pvc

+        persistentVolumeClaim:

+          {{if eq .Values.env "prod"}}

+          claimName: {{ .Values.pvc.prod | quote }}

+          {{ else }}

+          claimName: {{ .Values.pvc.dev | quote }}

+          {{ end }}

+      {{end}}

+      containers:

+      - name: {{ .Values.appName}}

+        image: {{ .Values.image}}

+        imagePullPolicy: Always

+        ports:

+        - name: https

+          containerPort: 8443

+          nodePort: {{.Values.nodePort}}

+          protocol: TCP

+        {{ if eq .Values.env "st"}}

+        resources:

+          limits: 

+            memory: "3Gi"

+            cpu: "1.8"

+          requests:

+            memory: "2Gi"

+            cpu: "1"

+        {{else}}  

+        resources:

+          limits:

+            memory: "6Gi"

+            cpu: "4"

+          requests:

+            memory: "2Gi"

+            cpu: "1.5"

+        {{ end }}

+        env:

+        - name: NAMESPACE

+          value: {{.Values.namespace}}

+        - name: APP_NAME

+          value: {{ .Values.appName}}

+        - name: AAF_PERM_TYPE

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.aafPermType.prod | quote }}

+          {{ else if  eq .Values.env "st"}}

+          value: {{ .Values.aafPermType.st | quote }}

+          {{ else }}

+          value: {{ .Values.aafPermType.dev | quote }}

+          {{ end }}                 

+        - name: AAF_ID

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_id

+              optional: true

+        - name: AAF_MECH_PASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_mech_password

+              optional: true

+        - name: AAF_PASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: aaf_password

+              optional: true

+        - name: CADI_KEYFILE

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedSecret}}

+              key: keyfile_secret_path

+              optional: true

+        - name: CADI_HOSTNAME

+          {{if eq .Values.env "prod"}}

+          value: {{ .Values.cadiHostname.prod | quote }}

+          {{else if  eq .Values.env "prod-dr"}}

+          value: {{ .Values.cadiHostname.prod_dr | quote }}

+          {{else if  eq .Values.env "st"}}

+          value: {{ .Values.cadiHostname.st | quote }} 

+          {{ else }}

+          value: {{ .Values.cadiHostname.dev | quote }}

+          {{ end }}

+        - name: APP_VERSION

+          value: {{.Values.version}}

+        - name: OTF_MONGO_HOSTS

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.otf.mongo.prod.host | quote }}

+          {{ else if eq  .Values.env "st" }}

+          value: {{ .Values.otf.mongo.st.host | quote }}

+          {{ else }}

+          value: {{ .Values.otf.mongo.dev.host | quote }}

+          {{ end }}

+        - name: OTF_MONGO_USERNAME

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.appName}}

+              key: mongo_username

+              optional: true

+        - name: OTF_MONGO_PASSWORD

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.appName}}

+              key: mongo_password

+              optional: true

+        - name: OTF_MONGO_REPLICASET

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.otf.mongo.prod.replicaSet | quote }}

+          {{else if  eq .Values.env "st"}}

+          value: {{ .Values.otf.mongo.st.replicaSet | quote }}

+          {{ else }}

+          value: {{ .Values.otf.mongo.dev.replicaSet | quote }}

+          {{ end }}

+        - name: OTF_MONGO_DATABASE

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.otf.mongo.prod.database | quote }}

+          {{else if  eq .Values.env "st"}}

+          value: {{ .Values.otf.mongo.st.database | quote }}

+          {{ else }}

+          value: {{ .Values.otf.mongo.dev.database | quote }}

+          {{ end }}

+        - name: otf.camunda.host

+          {{if eq .Values.env "prod"}}

+          value: {{ .Values.otf.camunda.prod.host | quote }}

+          {{ else if eq  .Values.env "prod-dr" }}

+          value: {{ .Values.otf.camunda.prod_dr.host | quote }}

+          {{ else if eq  .Values.env "st" }}

+          value: {{ .Values.otf.camunda.st.host | quote }}

+          {{ else }}

+          value: {{ .Values.otf.camunda.dev.host | quote }}

+          {{ end }}

+        - name: otf.camunda.port

+          {{if eq .Values.env "prod"}}

+          value: {{ .Values.otf.camunda.prod.port | quote }}

+          {{ else if eq  .Values.env "prod-dr" }}

+          value: {{ .Values.otf.camunda.prod_dr.port | quote }}

+          {{ else if eq .Values.env "st"}}

+          value: {{ .Values.otf.camunda.st.port | quote }}

+          {{ else }}

+          value: {{ .Values.otf.camunda.dev.port | quote }}

+          {{ end }}

+        - name: otf.camunda.executionUri

+          value: {{.Values.otf.camunda.executionUri | quote }}

+        - name: otf.camunda.pollingUri

+          value: {{.Values.otf.camunda.pollingUri | quote }}

+        - name: otf.camunda.deploymentUri

+          value: {{.Values.otf.camunda.deploymentUri | quote }}

+        - name: otf.camunda.processDefinitionKeyUri

+          value: {{.Values.otf.camunda.processDefinitionKeyUri | quote }}

+        - name: otf.camunda.deploymentDeletionUri

+          value: {{.Values.otf.camunda.deploymentDeletionUri | quote }}

+        - name: otf.camunda.testDefinitionDeletionUri

+          value: {{.Values.otf.camunda.testDefinitionDeletionUri | quote }}

+

+        - name: otf.camunda.uri.execute-test

+          value: {{.Values.otf.camunda.uri.execute_test | quote }}

+        - name: otf.camunda.uri.process-instance-completion-check

+          value: {{.Values.otf.camunda.uri.process_instance_completion_check | quote }}

+        - name: otf.camunda.uri.deploy-test-strategy-zip

+          value: {{.Values.otf.camunda.uri.deploy_test_strategy_zip | quote }}

+        - name: otf.camunda.uri.process-definition

+          value: {{.Values.otf.camunda.uri.process_definition | quote }}

+        - name: otf.camunda.uri.delete-test-strategy

+          value: {{.Values.otf.camunda.uri.delete_test_strategy | quote }}

+        - name: otf.camunda.uri.delete-test-strategy-test-definition-id

+          value: {{.Values.otf.camunda.uri.delete_test_strategy_test_definition_id | quote }}

+        - name: otf.camunda.uri.health

+          value: {{.Values.otf.camunda.uri.health | quote }}

+

+        - name: otf.api.poll-interval

+          value: {{.Values.otf.api.poll_interval | quote}}

+        - name: otf.api.poll-attempts

+          value: {{.Values.otf.api.poll_attempts | quote}}

+

+        - name: OTF_CERT_PATH

+          {{if or (eq .Values.env "prod") (eq .Values.env "prod-dr")}}

+          value: {{ .Values.cert.prod.path | quote }}

+          {{ else if eq .Values.env "st"}}

+          value: {{ .Values.cert.st.path | quote }}

+          {{ else }}

+          value: {{ .Values.cert.dev.path | quote }}

+          {{ end }}  

+        - name: OTF_CERT_PASS

+          valueFrom:

+            secretKeyRef:

+              name: {{ .Values.sharedCert}}

+              key: PKCS12_KEY

+              optional: true   

+        volumeMounts:

+        - name: {{.Values.appName}}-keyfile-volume

+          mountPath: /opt/secret

+        - name: {{.Values.appName}}-cert-volume

+          mountPath: /opt/cert

+        {{ if or (eq .Values.env "st") (eq .Values.env "prod-dr")}}

+        {{else}}

+        - name: logging-pvc

+          mountPath: "/otf/logs"

+        {{end}} 

+        livenessProbe:

+          httpGet:

+            path: /otf/api/health/v1

+            port: https

+            scheme: HTTPS

+            httpHeaders:

+            - name: X-Custom-Header

+              value: Alive

+          initialDelaySeconds: 30

+          timeoutSeconds: 30

+          periodSeconds: 30

+        readinessProbe:

+          httpGet:

+            path: /otf/api/health/v1

+            port: https

+            scheme: HTTPS

+            httpHeaders:

+            - name: X-Custom-Header

+              value: Ready

+          initialDelaySeconds: 30

+          timeoutSeconds: 30

+          periodSeconds: 30

+      restartPolicy: Always

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
index 0000000..bc77345
--- /dev/null
+++ b/otf-service-api/helm/otf-service-api/templates/secret.yaml
@@ -0,0 +1,8 @@
+apiVersion: v1

+kind: Secret

+metadata:

+  name: {{ .Values.appName}}

+type: Opaque

+data:

+  mongo_username: {{ .Values.otf.mongo.username | b64enc}}

+  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
index 0000000..38acf3d
--- /dev/null
+++ b/otf-service-api/helm/otf-service-api/templates/service.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1

+kind: Service

+metadata:

+  name: {{ .Values.appName }}

+  namespace: {{ .Values.namespace}}

+  labels:

+    app: {{ .Values.appName }}

+    version: {{ .Values.version}}

+spec:

+  type: NodePort

+  ports:

+  - name: https

+    port: 8443

+    protocol: TCP

+    nodePort: {{ .Values.nodePort}}

+  selector:

+    app: {{ .Values.appName }}

+    version: {{ .Values.version}}

diff --git a/otf-service-api/pom.xml b/otf-service-api/pom.xml
new file mode 100644
index 0000000..fda9ed3
--- /dev/null
+++ b/otf-service-api/pom.xml
@@ -0,0 +1,348 @@
+<?xml version="1.0" encoding="UTF-8"?>

+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

+  xmlns="http://maven.apache.org/POM/4.0.0"

+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

+  <artifactId>otf-service-api</artifactId>

+  <build>

+    <finalName>otf-service-api</finalName>

+    <plugins>

+      <plugin>

+        <artifactId>maven-compiler-plugin</artifactId>

+        <configuration>

+          <source>1.8</source>

+          <target>1.8</target>

+        </configuration>

+        <groupId>org.apache.maven.plugins</groupId>

+      </plugin>

+      <plugin>

+        <artifactId>spring-boot-maven-plugin</artifactId>

+        <groupId>org.springframework.boot</groupId>

+      </plugin>

+      <plugin>

+        <artifactId>swagger-maven-plugin</artifactId>

+        <configuration>

+          <outputFileName>openapi</outputFileName>

+          <!--<outputPath>${project.build.directory}/generatedtest</outputPath>-->

+          <outputFormat>JSONANDYAML</outputFormat>

+          <prettyPrint>true</prettyPrint>

+          <resourcePackages>

+            <package>org.oran.otf.api</package>

+          </resourcePackages>

+        </configuration>

+        <executions>

+          <execution>

+            <goals>

+              <goal>resolve</goal>

+            </goals>

+            <phase>compile</phase>

+          </execution>

+        </executions>

+        <groupId>io.swagger.core.v3</groupId>

+        <version>2.0.7</version>

+      </plugin>

+

+      <plugin>

+        <groupId>org.apache.maven.plugins</groupId>

+        <artifactId>maven-surefire-plugin</artifactId>

+        <version>2.22.1</version>

+        <configuration>

+          <skipTests>${skipUTs}</skipTests>

+        </configuration>

+      </plugin>

+      <plugin>

+        <groupId>org.apache.maven.plugins</groupId>

+        <artifactId>maven-failsafe-plugin</artifactId>

+        <version>2.22.1</version>

+        <executions>

+          <execution>

+            <id>run-integration-tests</id>

+            <phase>integration-test</phase>

+            <goals>

+              <goal>integration-test</goal>

+              <goal>verify</goal>

+            </goals>

+          </execution>

+        </executions>

+        <configuration>

+          <skipTests>${skipTests}</skipTests>

+          <skipITs>${skipITs}</skipITs>

+        </configuration>

+      </plugin>

+

+    </plugins>

+    <resources>

+      <resource>

+        <directory>src/main/resources</directory>

+        <excludes>

+          <exclude>otf_dev.p12</exclude>

+        </excludes>

+        <filtering>true</filtering>

+        <includes>

+          <include>**/*</include>

+        </includes>

+        <targetPath>${basedir}/target/src/main/resources</targetPath>

+      </resource>

+      <resource>

+        <directory>src/main/resources</directory>

+        <excludes>

+          <exclude>otf_dev.p12</exclude>

+        </excludes>

+        <filtering>true</filtering>

+        <includes>

+          <include>**/*</include>

+        </includes>

+      </resource>

+      <resource>

+        <directory>src/main/resources</directory>

+        <includes>

+          <include>otf_dev.p12</include>

+        </includes>

+        <targetPath>${basedir}/target/src/main/resources</targetPath>

+      </resource>

+      <resource>

+        <directory>src/main/resources</directory>

+        <includes>

+          <include>otf_dev.p12</include>

+        </includes>

+      </resource>

+      <resource>

+        <directory>docker</directory>

+        <includes>

+          <include>Dockerfile</include>

+        </includes>

+        <targetPath>${basedir}/target</targetPath>

+      </resource>

+    </resources>

+  </build>

+  <dependencies>

+    <dependency>

+      <artifactId>spring-boot-starter</artifactId>

+      <groupId>org.springframework.boot</groupId>

+    </dependency>

+

+    <dependency>

+      <artifactId>spring-boot-starter-web</artifactId>

+      <groupId>org.springframework.boot</groupId>

+    </dependency>

+

+    <dependency>

+      <artifactId>spring-boot-starter-jersey</artifactId>

+      <groupId>org.springframework.boot</groupId>

+    </dependency>

+

+

+    <dependency>

+      <groupId>org.glassfish.jersey.test-framework</groupId>

+      <artifactId>jersey-test-framework-core</artifactId>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>org.glassfish.jersey.test-framework.providers</groupId>

+      <artifactId>jersey-test-framework-provider-grizzly2</artifactId>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>de.flapdoodle.embed</groupId>

+      <artifactId>de.flapdoodle.embed.mongo</artifactId>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>com.github.tomakehurst</groupId>

+      <artifactId>wiremock-jre8</artifactId>

+      <version>2.24.0</version>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>org.mockito</groupId>

+      <artifactId>mockito-core</artifactId>

+      <version>2.15.0</version>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>org.mockito</groupId>

+      <artifactId>mockito-inline</artifactId>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>io.rest-assured</groupId>

+      <artifactId>rest-assured</artifactId>

+      <version>4.0.0</version>

+      <scope>test</scope>

+    </dependency>

+    <dependency>

+      <groupId>io.rest-assured</groupId>

+      <artifactId>rest-assured-all</artifactId>

+      <version>4.0.0</version>

+      <scope>test</scope>

+    </dependency>

+

+

+

+    <dependency>

+      <artifactId>spring-boot-starter-test</artifactId>

+      <groupId>org.springframework.boot</groupId>

+      <scope>test</scope>

+      <exclusions>

+        <exclusion>

+          <groupId>com.vaadin.external.google</groupId>

+          <artifactId>android-json</artifactId>

+        </exclusion>

+      </exclusions>

+    </dependency>

+

+    <dependency>

+      <artifactId>spring-boot-starter-data-mongodb</artifactId>

+      <groupId>org.springframework.boot</groupId>

+    </dependency>

+

+    <dependency>

+      <artifactId>swagger-jaxrs2</artifactId>

+      <groupId>io.swagger.core.v3</groupId>

+      <version>2.0.7</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>swagger-jaxrs2-servlet-initializer</artifactId>

+      <groupId>io.swagger.core.v3</groupId>

+      <version>2.0.7</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>swagger-annotations</artifactId>

+      <groupId>io.swagger.core.v3</groupId>

+      <version>2.0.7</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>springfox-swagger2</artifactId>

+      <groupId>io.springfox</groupId>

+      <version>2.9.2</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>springfox-swagger-ui</artifactId>

+      <groupId>io.springfox</groupId>

+      <version>2.9.2</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>springfox-bean-validators</artifactId>

+      <groupId>io.springfox</groupId>

+      <version>2.9.2</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>httpclient</artifactId>

+      <groupId>org.apache.httpcomponents</groupId>

+    </dependency>

+

+    <dependency>

+      <artifactId>h2</artifactId>

+      <groupId>com.h2database</groupId>

+    </dependency>

+

+<!--    <dependency>-->

+<!--      <artifactId>wiremock</artifactId>-->

+<!--      <groupId>com.github.tomakehurst</groupId>-->

+<!--      <version>1.58</version>-->

+<!--    </dependency>-->

+

+    <dependency>

+      <artifactId>gson</artifactId>

+      <groupId>com.google.code.gson</groupId>

+      <version>2.8.5</version>

+    </dependency>

+

+    <!-- CADI AAF Dependencies !! -->

+    <dependency>

+      <artifactId>aaf-auth-client</artifactId>

+      <groupId>org.onap.aaf.authz</groupId>

+      <version>${cadi.version}</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>aaf-cadi-core</artifactId>

+      <groupId>org.onap.aaf.authz</groupId>

+      <version>${cadi.version}</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>aaf-cadi-aaf</artifactId>

+      <groupId>org.onap.aaf.authz</groupId>

+      <version>${cadi.version}</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>json</artifactId>

+      <groupId>org.json</groupId>

+      <version>20180813</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>jackson-annotations</artifactId>

+      <groupId>com.fasterxml.jackson.core</groupId>

+    </dependency>

+

+    <dependency>

+      <artifactId>jackson-core</artifactId>

+      <groupId>com.fasterxml.jackson.core</groupId>

+    </dependency>

+

+    <dependency>

+      <artifactId>jackson-databind</artifactId>

+      <groupId>com.fasterxml.jackson.core</groupId>

+    </dependency>

+

+    <dependency>

+      <artifactId>jersey-media-multipart</artifactId>

+      <groupId>org.glassfish.jersey.media</groupId>

+      <version>2.27</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>httpmime</artifactId>

+      <groupId>org.apache.httpcomponents</groupId>

+      <version>4.5.7-SNAPSHOT</version>

+    </dependency>

+

+    <dependency>

+      <artifactId>httpasyncclient</artifactId>

+      <groupId>org.apache.httpcomponents</groupId>

+      <version>4.1.4</version>

+    </dependency>

+      <dependency>

+          <groupId>net.java.dev.jna</groupId>

+          <artifactId>jna-platform</artifactId>

+      </dependency>

+  </dependencies>

+

+  <description>Service API - OTF</description>

+  <groupId>org.oran.otf</groupId>

+

+  <modelVersion>4.0.0</modelVersion>

+

+  <name>otf-service-api</name>

+

+  <packaging>jar</packaging>

+

+  <parent>

+    <artifactId>spring-boot-starter-parent</artifactId>

+    <groupId>org.springframework.boot</groupId>

+    <version>2.1.3.RELEASE</version>

+  </parent>

+

+  <properties>

+    <skipTests>false</skipTests>

+    <skipITs>${skipTests}</skipITs>

+    <skipUTs>${skipTests}</skipUTs>

+    <cadi.version>2.1.10</cadi.version>

+    <docker.registry>registry.hub.docker.io</docker.registry>

+    <java.version>1.8</java.version>

+    <namespace>org.oran.otf</namespace>

+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

+  </properties>

+  <version>Camille.1.0</version>

+

+

+</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
index 0000000..8836555
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/Application.java
@@ -0,0 +1,74 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api;

+

+import static com.google.common.collect.Sets.newHashSet;

+

+import io.swagger.v3.oas.annotations.OpenAPIDefinition;

+import io.swagger.v3.oas.annotations.info.Contact;

+import io.swagger.v3.oas.annotations.info.Info;

+import org.apache.commons.logging.Log;

+import org.apache.commons.logging.LogFactory;

+import org.springframework.boot.SpringApplication;

+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

+import org.springframework.boot.autoconfigure.SpringBootApplication;

+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;

+import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;

+import org.springframework.context.ApplicationContext;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.ComponentScan;

+import org.springframework.context.annotation.Configuration;

+import springfox.documentation.builders.PathSelectors;

+import springfox.documentation.spi.DocumentationType;

+import springfox.documentation.spring.web.plugins.Docket;

+import springfox.documentation.swagger2.annotations.EnableSwagger2;

+

+@SpringBootApplication

+@Configuration

+@EnableAutoConfiguration(

+    exclude = {

+      ErrorMvcAutoConfiguration.class,

+      DataSourceAutoConfiguration.class,

+      HibernateJpaAutoConfiguration.class,

+    })

+@ComponentScan(basePackages = "org.oran.otf")

+@EnableSwagger2

+@OpenAPIDefinition(

+    info =

+        @Info(

+            title = "Open Test Framework API",

+            version = "1.0",

+            description = "A RESTful API used to communicate with the OTF test control unit.",

+            contact = @Contact(url = "https://localhost:32524", name = "OTF")))

+public class Application {

+  private static final Log log = LogFactory.getLog(Application.class);

+

+  public static void main(String[] args) {

+    ApplicationContext ctx = SpringApplication.run(Application.class, args);

+  }

+

+  @Bean

+  public Docket testInstanceApi() {

+    return new Docket(DocumentationType.SWAGGER_2)

+        .select()

+        // .apis(testInstancePath())

+        .paths(PathSelectors.any())

+        .build()

+        .protocols(newHashSet("https"));

+  }

+}

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
index 0000000..1279688
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/Utilities.java
@@ -0,0 +1,394 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api;

+

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.model.local.OTFApiResponse;

+import org.oran.otf.common.repository.UserRepository;

+import com.fasterxml.jackson.databind.DeserializationFeature;

+import com.fasterxml.jackson.databind.ObjectMapper;

+import com.google.common.base.Strings;

+import com.google.gson.JsonObject;

+import com.google.gson.JsonParseException;

+import com.google.gson.JsonParser;

+import java.io.IOException;

+import java.io.PrintWriter;

+import java.io.StringWriter;

+import java.util.Arrays;

+import java.util.Base64;

+import java.util.Date;

+import java.util.List;

+import java.util.Map;

+import java.util.Optional;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+import org.apache.http.HttpEntity;

+import org.apache.http.HttpResponse;

+import org.apache.http.NameValuePair;

+import org.apache.http.client.HttpClient;

+import org.apache.http.client.entity.UrlEncodedFormEntity;

+import org.apache.http.client.methods.HttpDelete;

+import org.apache.http.client.methods.HttpGet;

+import org.apache.http.client.methods.HttpPost;

+import org.apache.http.conn.ssl.NoopHostnameVerifier;

+import org.apache.http.entity.StringEntity;

+import org.apache.http.impl.client.HttpClientBuilder;

+import org.apache.http.message.BasicNameValuePair;

+import org.apache.http.util.EntityUtils;

+import org.bson.types.ObjectId;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public class Utilities {

+

+  public static JsonObject parseJson(String str) {

+    try {

+      return new JsonParser().parse(str).getAsJsonObject();

+    } catch (JsonParseException jpe) {

+      logger.error("Cannot parse string as Json.");

+      return null;

+    }

+  }

+

+  public static class Http {

+    public static class BuildResponse {

+      public static Response badRequest() {

+        return Response.status(400).build();

+      }

+

+      public static Response badRequestWithMessage(String msg) {

+        return Response.status(400)

+            .type(MediaType.APPLICATION_JSON)

+            .entity(new OTFApiResponse(400, msg))

+            .build();

+      }

+

+      public static Response internalServerError() {

+        return Response.status(500).build();

+      }

+

+      public static Response internalServerErrorWithMessage(String msg) {

+        return Response.status(500)

+            .type(MediaType.APPLICATION_JSON)

+            .entity(new OTFApiResponse(500, msg))

+            .build();

+      }

+

+      public static Response unauthorized() {

+        return Response.status(401).build();

+      }

+

+      public static Response unauthorizedWithMessage(String msg) {

+        return Response.status(401)

+            .type(MediaType.APPLICATION_JSON)

+            .entity(new OTFApiResponse(401, msg))

+            .build();

+      }

+    }

+

+    public static HttpResponse httpPostJsonUsingAAF(String url, String body) throws Exception {

+      HttpResponse response = null;

+

+        String aafCredentialsDecoded =

+            System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");

+

+        HttpPost post = new HttpPost(url);

+        post.setHeader("Content-Type", MediaType.APPLICATION_JSON);

+        post.setHeader(

+            "Authorization",

+            "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()));

+        post.setEntity(new StringEntity(body));

+

+        HttpClient client =

+            HttpClientBuilder.create()

+                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)

+                .build();

+        response = client.execute(post);

+

+        // logger.info(String.format("[POST:%s]\n %s", url, body));

+

+      return response;

+    }

+

+    public static HttpResponse httpDeleteAAF(String url) {

+      HttpResponse response = null;

+

+      try {

+        String aafCredentialsDecoded =

+            System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");

+

+        HttpDelete delete = new HttpDelete(url);

+        delete.setHeader(

+            "Authorization",

+            "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()));

+        HttpClient client =

+            HttpClientBuilder.create()

+                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)

+                .build();

+        response = client.execute(delete);

+

+        // logger.info(String.format("[DELETE:%s]\n", url));

+      } catch (Exception e) {

+        e.printStackTrace();

+      }

+

+      return response;

+    }

+

+    public static HttpResponse httpPostXmlUsingAAF(String url, String body) {

+      HttpResponse response = null;

+

+      try {

+        String aafCredentialsDecoded =

+            System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");

+

+        HttpPost post = new HttpPost(url);

+        post.setHeader("Content-Type", MediaType.APPLICATION_JSON);

+        post.setHeader(

+            "Authorization",

+            "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()));

+        post.setEntity(new StringEntity(body));

+

+        List<NameValuePair> urlParameters = Arrays.asList(new BasicNameValuePair("xml", body));

+        post.setEntity(new UrlEncodedFormEntity(urlParameters));

+

+        HttpClient client = HttpClientBuilder.create().build();

+        response = client.execute(post);

+

+        logger.info(String.format("[POST:%s]\n %s", url, body));

+      } catch (Exception e) {

+        e.printStackTrace();

+      }

+

+      return response;

+    }

+

+    public static HttpResponse httpGetUsingAAF(String url) {

+      HttpResponse response = null;

+

+      try {

+        String aafCredentialsDecoded =

+            System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");

+

+        HttpGet get = new HttpGet(url);

+        get.setHeader("Content-Type", "application/json");

+        get.setHeader(

+            "Authorization",

+            "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()));

+

+        HttpClient client =

+            HttpClientBuilder.create()

+                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)

+                .build();

+        response = client.execute(get);

+

+        logger.info(String.format("[GET:%s]", url));

+      } catch (Exception e) {

+        e.printStackTrace();

+      }

+

+      return response;

+    }

+  }

+

+  public static class Camunda {

+

+    public static boolean isCamundaOnline() {

+      final String healthUrl =

+          String.format(

+              "%s:%s/%s",

+              System.getenv("otf.camunda.host"),

+              System.getenv("otf.camunda.port"),

+              System.getenv("otf.camunda.uri.health"));

+

+      HttpResponse res = Utilities.Http.httpGetUsingAAF(healthUrl);

+      return res != null && res.getStatusLine().getStatusCode() == 200;

+    }

+

+    public static JsonObject processInstanceStatus(String executionId) {

+      // Read necessary environment variables - Avoiding using Spring dependencies (@Value)

+      String host = System.getenv("otf.camunda.host");

+      String path = System.getenv("otf.camunda.uri.process-instance-completion-check");

+      int port = Utilities.TryGetEnvironmentVariable("otf.camunda.port");

+

+      if (!Utilities.isHostValid(host)) {

+        logger.error("Host (%s) must use either the http or https protocol.", host);

+        return null;

+      }

+

+      if (!Utilities.isPortValid(port)) {

+        logger.error(

+            "Invalid port (%s) specified as environment variable 'otf.camunda.port'.",

+            System.getenv("otf.camunda.port"));

+        return null;

+      }

+      try {

+        String getUrl = String.format("%s:%s/%s/%s", host, port, path, executionId);

+        HttpResponse response = Utilities.Http.httpGetUsingAAF(getUrl);

+        HttpEntity entity = response.getEntity();

+        String result = EntityUtils.toString(entity);

+

+        return parseJson(result);

+      } catch (IOException ioe) {

+        Utilities.printStackTrace(ioe, Utilities.LogLevel.ERROR);

+        logger.error("Cannot convert http entity to String.");

+      } catch (Exception e) {

+        Utilities.printStackTrace(e, Utilities.LogLevel.ERROR);

+      }

+      // conversion was unsuccessful

+      return null;

+    }

+  }

+

+  private static final Logger logger = LoggerFactory.getLogger(Utilities.class);

+

+  public static void printStackTrace(Exception exception, LogLevel logLevel) {

+    String stackTrace = getStackTrace(exception);

+

+    switch (logLevel) {

+      case INFO:

+        logger.info(stackTrace);

+        break;

+      case WARN:

+        logger.warn(stackTrace);

+        break;

+      case DEBUG:

+        logger.debug(stackTrace);

+        break;

+      case ERROR:

+        logger.error(stackTrace);

+        break;

+    }

+  }

+

+  public static int TryGetEnvironmentVariable(String variable) {

+    String value = System.getenv(variable);

+    int result = 0x80000000;

+

+    try {

+      result = Integer.parseInt(value);

+    } catch (NumberFormatException error) {

+      error.printStackTrace();

+      logger.error(error.getMessage());

+    }

+

+    return result;

+  }

+

+  public static String getStackTrace(Exception exception) {

+    StringWriter stringWriter = new StringWriter();

+    exception.printStackTrace(new PrintWriter(stringWriter));

+    return stringWriter.toString();

+  }

+

+  public static boolean isObjectIdValid(String input) {

+    ObjectId id = null;

+    try {

+      id = new ObjectId(input); // check if an ObjectId can be created from the string

+      if (id.toString().equalsIgnoreCase(input)) return true;

+      logger.warn("The input string does not have the same value as it's string representation.");

+    } catch (IllegalArgumentException e) {

+      logger.error(String.format("An ObjectId cannot be instantiated from the string: %s", input));

+    }

+

+    return false;

+  }

+

+  public static boolean isPortValid(int port) {

+    return (port >= 0 && port <= 65535);

+  }

+

+  public static boolean isHostValid(String host) {

+    return host.startsWith("http");

+  }

+

+  public static <T> boolean identifierExistsInCollection(

+      MongoRepository<T, String> repository, ObjectId identifier) {

+    return repository.findById(identifier.toString()).isPresent();

+  }

+

+  public static <T> T findByIdGeneric(MongoRepository<T, String> repository, ObjectId identifier) {

+    Optional<T> optionalObj = repository.findById(identifier.toString());

+    return optionalObj.orElse(null);

+  }

+

+  public static String[] decodeBase64AuthorizationHeader(String encodedHeader) {

+    try {

+      byte[] decodedAuthorization = Base64.getDecoder().decode(encodedHeader.replace("Basic ", ""));

+      String credentials = new String(decodedAuthorization);

+      return credentials.split(":");

+    } catch (Exception e) {

+      logger.error("Unable to decode authorization header: " + encodedHeader);

+      return null;

+    }

+  }

+

+  public static User findUserByMechanizedId(String mechanizedId, UserRepository userRepository) {

+    Optional<User> optionalUser = userRepository.findFirstByEmail(mechanizedId);

+    return optionalUser.orElse(null);

+  }

+

+  public static User findUserByAuthHeader(String authorization, UserRepository userRepository) {

+    try {

+      if (Strings.isNullOrEmpty(authorization)) {

+        return null;

+      }

+      String[] credentials = Utilities.decodeBase64AuthorizationHeader(authorization);

+      return findUserByMechanizedId(credentials[0], userRepository);

+    } catch (Exception e) {

+      return null;

+    }

+  }

+

+  public static <T> T resolveOptional(Optional<T> optional) {

+    return optional.orElse(null);

+  }

+

+  public static <T> T mapRequest(Class<T> targetType, String input) {

+    logger.info(targetType.getName());

+

+    ObjectMapper mapper =

+        new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

+    try {

+      return mapper.readValue(input, targetType);

+    } catch (IOException e) {

+      Utilities.printStackTrace(e, LogLevel.ERROR);

+      return null;

+    }

+  }

+

+  public enum LogLevel {

+    WARN,

+    DEBUG,

+    INFO,

+    ERROR

+  }

+

+  public static Date getCurrentDate() {

+    return new Date(System.currentTimeMillis());

+  }

+

+  public static Map<String, Object> replaceObjectId(Map<String, Object> map, String objectIdKey) {

+    if (map.containsKey(objectIdKey)) {

+      ObjectId id = (ObjectId) map.get(objectIdKey);

+      map.replace(objectIdKey, id.toString());

+    }

+

+    return map;

+  }

+}

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
index 0000000..d98b9ed
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/config/CadiFilterConfiguration.java
@@ -0,0 +1,119 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.config;

+

+import javax.servlet.Filter;

+import org.onap.aaf.cadi.Access;

+import org.onap.aaf.cadi.config.Config;

+import org.onap.aaf.cadi.filter.CadiFilter;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

+import org.springframework.boot.web.servlet.FilterRegistrationBean;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Conditional;

+import org.springframework.context.annotation.PropertySource;

+import org.springframework.stereotype.Component;

+

+@PropertySource("classpath:application.properties")

+@Component

+@ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true",matchIfMissing = true)

+public class CadiFilterConfiguration {

+

+  @Value("${aaf.call-timeout}")

+  private String AAF_CALL_TIMEOUT;

+

+  @Value("${aaf.conn-timeout}")

+  private String AAF_CONN_TIMEOUT;

+

+  @Value("${aaf.default-realm}")

+  private String AAF_DEFAULT_REALM;

+

+  @Value("${aaf.env}")

+  private String AAF_ENV;

+

+  @Value("${aaf.locate-url}")

+  private String AAF_LOCATE_URL;

+

+  @Value("${aaf.lur-class}")

+  private String AAF_LUR_CLASS;

+

+  @Value("${aaf.url}")

+  private String AAF_URL;

+

+  @Value("${basic-realm}")

+  private String BASIC_REALM;

+

+  @Value("${basic-warn}")

+  private String BASIC_WARN;

+

+  @Value("${cadi-latitude}")

+  private String CADI_LATITUDE;

+

+  @Value("${cadi-longitude}")

+  private String CADI_LONGITUDE;

+

+  @Value("${cadi-protocols}")

+  private String CADI_PROTOCOLS;

+

+  @Value("${cadi-noauthn}")

+  private String CADI_NOAUTHN;

+

+  @Bean(name = "cadiFilterRegistrationBean")

+  @ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true",matchIfMissing = true)

+  public FilterRegistrationBean<Filter> cadiFilterRegistration() {

+    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();

+    // set cadi configuration properties

+    initCadiProperties(registration);

+

+    registration.addUrlPatterns("/otf/api/testInstance/*", "/otf/api/testExecution/*", "/otf/api/testStrategy/*", "/otf/api/virtualTestHead/*");

+    registration.setFilter(cadiFilter());

+    registration.setName("otfCadiFilter");

+    registration.setOrder(0);

+    return registration;

+  }

+

+  public Filter cadiFilter() {

+    return new CadiFilter();

+  }

+

+  private void initCadiProperties(FilterRegistrationBean<Filter> registration) {

+    registration.addInitParameter(Config.AAF_APPID, System.getenv("AAF_ID"));

+    registration.addInitParameter(Config.AAF_APPPASS, System.getenv("AAF_PASSWORD"));

+    registration.addInitParameter(Config.AAF_CALL_TIMEOUT, AAF_CALL_TIMEOUT);

+    registration.addInitParameter(Config.AAF_CONN_TIMEOUT, AAF_CONN_TIMEOUT);

+    registration.addInitParameter(Config.AAF_DEFAULT_REALM, AAF_DEFAULT_REALM);

+    registration.addInitParameter(Config.AAF_ENV, AAF_ENV);

+    registration.addInitParameter(Config.AAF_LOCATE_URL, AAF_LOCATE_URL);

+    registration.addInitParameter(Config.AAF_LUR_CLASS, AAF_LUR_CLASS);

+    registration.addInitParameter(

+        Config.AAF_URL, AAF_URL);

+

+    registration.addInitParameter(Config.BASIC_REALM, BASIC_REALM);

+    registration.addInitParameter(Config.BASIC_WARN, BASIC_WARN);

+

+    registration.addInitParameter(Config.CADI_KEYFILE, System.getenv("CADI_KEYFILE"));

+    registration.addInitParameter(Config.CADI_LATITUDE, CADI_LATITUDE);

+    //registration.addInitParameter(Config.CADI_LOGLEVEL, Access.Level.DEBUG.name());

+    registration.addInitParameter(Config.CADI_LONGITUDE, CADI_LONGITUDE);

+    registration.addInitParameter(Config.CADI_NOAUTHN, CADI_NOAUTHN);

+    registration.addInitParameter(Config.CADI_PROTOCOLS, CADI_PROTOCOLS);

+    registration.addInitParameter(Config.CADI_KEYSTORE, System.getenv("OTF_CERT_PATH"));

+    registration.addInitParameter(Config.CADI_KEYSTORE_PASSWORD, System.getenv("OTF_CERT_PASS"));

+

+    registration.addInitParameter(Config.HOSTNAME, System.getenv("CADI_HOSTNAME"));

+  }

+}

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
index 0000000..ef7fae5
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/config/CombinedResourceProvider.java
@@ -0,0 +1,46 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.config;

+

+import java.util.List;

+import java.util.stream.Collectors;

+import java.util.stream.Stream;

+import javax.annotation.Resource;

+import org.springframework.context.annotation.Primary;

+import org.springframework.stereotype.Component;

+import springfox.documentation.swagger.web.InMemorySwaggerResourcesProvider;

+import springfox.documentation.swagger.web.SwaggerResource;

+import springfox.documentation.swagger.web.SwaggerResourcesProvider;

+

+@Component

+@Primary

+public class CombinedResourceProvider implements SwaggerResourcesProvider {

+

+  @Resource private InMemorySwaggerResourcesProvider inMemorySwaggerResourcesProvider;

+

+  public List<SwaggerResource> get() {

+

+    SwaggerResource jerseySwaggerResource = new SwaggerResource();

+    jerseySwaggerResource.setLocation("/otf/api/openapi.json");

+    jerseySwaggerResource.setSwaggerVersion("2.0");

+    jerseySwaggerResource.setName("Service API");

+

+    return Stream.concat(

+            Stream.of(jerseySwaggerResource), inMemorySwaggerResourcesProvider.get().stream())

+        .collect(Collectors.toList());

+  }

+}

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
index 0000000..0546a7d
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/config/DataConfig.java
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.config;

+

+import com.mongodb.MongoClient;

+import com.mongodb.MongoClientOptions;

+import com.mongodb.MongoCredential;

+import com.mongodb.ServerAddress;

+import java.util.ArrayList;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.context.annotation.Profile;

+import org.springframework.data.mongodb.config.AbstractMongoConfiguration;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

+

+@Configuration

+@EnableMongoRepositories(basePackages = "org.oran.otf.common.repository")

+@Profile("!test")

+public class DataConfig extends AbstractMongoConfiguration {

+

+  @Value("${otf.mongo.hosts}")

+  private String hosts;

+

+  @Value("${otf.mongo.username}")

+  private String username;

+

+  @Value("${otf.mongo.password}")

+  private String password;

+

+  @Value("${otf.mongo.replicaSet}")

+  private String replicaSet;

+

+  @Value("${otf.mongo.database}")

+  private String database;

+

+  public DataConfig() {}

+

+  @Override

+  protected String getDatabaseName() {

+    return database;

+  }

+

+  @Override

+  public MongoClient mongoClient() {

+    MongoCredential credential =

+        MongoCredential.createScramSha1Credential(username, database, password.toCharArray());

+

+    MongoClientOptions options =

+        MongoClientOptions.builder().sslEnabled(false).requiredReplicaSetName(replicaSet).build();

+

+    String[] hostArray = hosts.split(",");

+    ArrayList<ServerAddress> hosts = new ArrayList<>();

+

+    for (String host : hostArray) {

+      String[] hostSplit = host.split(":");

+      hosts.add(new ServerAddress(hostSplit[0], Integer.parseInt(hostSplit[1])));

+    }

+

+    return new MongoClient(hosts, credential, options);

+  }

+

+  @Override

+  @Bean

+  public MongoTemplate mongoTemplate() {

+    return new MongoTemplate(mongoClient(), database);

+  }

+}

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
index 0000000..2646431
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/config/HttpSecurityConfiguration.java
@@ -0,0 +1,68 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.config;

+

+import org.apache.catalina.Context;

+import org.apache.catalina.connector.Connector;

+import org.apache.tomcat.util.descriptor.web.SecurityCollection;

+import org.apache.tomcat.util.descriptor.web.SecurityConstraint;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.context.properties.EnableConfigurationProperties;

+import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;

+import org.springframework.boot.web.servlet.server.ServletWebServerFactory;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+

+@Configuration

+@EnableConfigurationProperties

+public class HttpSecurityConfiguration {

+  @Value("${server.port.http}")

+  private int httpPort;

+

+  @Value("${server.port}")

+  private int httpsPort;

+

+  @Value("${ssl.flag}")

+  private boolean httpsOnly;

+

+  @Bean

+  public ServletWebServerFactory servletContainer() {

+    TomcatServletWebServerFactory tomcat =

+        new TomcatServletWebServerFactory(){

+          @Override

+          protected void postProcessContext(Context context) {

+            SecurityConstraint securityConstraint = new SecurityConstraint();

+            if(httpsOnly){ securityConstraint.setUserConstraint("CONFIDENTIAL");}

+            SecurityCollection collection = new SecurityCollection();

+            collection.addPattern("/*");

+            securityConstraint.addCollection(collection);

+            context.addConstraint(securityConstraint);

+          }

+        };

+    tomcat.addAdditionalTomcatConnectors(redirectConnector());

+    return tomcat;

+  }

+

+  private Connector redirectConnector() {

+    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");

+    connector.setScheme("http");

+    connector.setPort(httpPort);

+    connector.setSecure(false);

+    if(httpsOnly) { connector.setRedirectPort(httpsPort); }

+    return connector;

+  }

+}

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
index 0000000..6d06ac7
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/config/JerseyConfiguration.java
@@ -0,0 +1,99 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.config;

+

+import org.oran.otf.api.service.impl.*;

+import com.fasterxml.jackson.annotation.JsonInclude;

+import com.fasterxml.jackson.databind.DeserializationFeature;

+import com.fasterxml.jackson.databind.MapperFeature;

+import com.fasterxml.jackson.databind.ObjectMapper;

+import com.fasterxml.jackson.databind.SerializationFeature;

+import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;

+

+import java.util.logging.Level;

+import java.util.logging.Logger;

+import javax.ws.rs.ApplicationPath;

+import org.glassfish.jersey.logging.LoggingFeature;

+import org.glassfish.jersey.media.multipart.MultiPartFeature;

+import org.glassfish.jersey.server.ResourceConfig;

+import org.glassfish.jersey.server.ServerProperties;

+import org.glassfish.jersey.servlet.ServletProperties;

+import org.oran.otf.api.service.impl.*;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Primary;

+import org.springframework.stereotype.Component;

+

+@Component

+@ApplicationPath("/otf/api")

+public class JerseyConfiguration extends ResourceConfig {

+  private static final Logger log = Logger.getLogger(JerseyConfiguration.class.getName());

+

+  //   @Value("${spring.jersey.application-path}")

+  //   private String apiPath;

+

+  //  @Value("${springfox.documentation.swagger.v2.path}")

+  //  private String swagger2Endpoint;

+

+  @Autowired

+  public JerseyConfiguration() {

+    registerFeatures();

+    registerEndpoints();

+    setProperties();

+

+    configureSwagger();

+  }

+

+

+  private void registerFeatures() {

+    register(MultiPartFeature.class);

+    register(new OTFLoggingFeature(Logger.getLogger(getClass().getName()), Level.INFO, LoggingFeature.Verbosity.PAYLOAD_ANY, 8192));

+

+  }

+

+  private void registerEndpoints() {

+    register(TestInstanceServiceImpl.class);

+    register(HealthServiceImpl.class);

+    register(TestStrategyServiceImpl.class);

+    register(TestExecutionServiceImpl.class);

+    register(VirtualTestHeadServiceImpl.class);

+

+    register(OtfOpenServiceImpl.class);

+  }

+

+  private void setProperties() {

+    property(ServletProperties.FILTER_FORWARD_ON_404, true);

+    property(ServerProperties.RESPONSE_SET_STATUS_OVER_SEND_ERROR, true);

+  }

+

+  private void configureSwagger() {

+    OpenApiResource openApiResource = new OpenApiResource();

+

+    register(openApiResource);

+  }

+

+  @Bean

+  @Primary

+  public ObjectMapper objectMapper() {

+    ObjectMapper objectMapper = new ObjectMapper();

+    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

+    objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

+    objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES);

+    objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);

+    return objectMapper;

+  }

+}

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
index 0000000..aba17f0
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/config/OTFApiEnforcementFilter.java
@@ -0,0 +1,134 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.config;

+

+import com.google.common.base.Strings;

+import java.io.IOException;

+import java.util.ArrayList;

+import java.util.List;

+import java.util.Map;

+import java.util.TreeMap;

+import javax.servlet.Filter;

+import javax.servlet.FilterChain;

+import javax.servlet.FilterConfig;

+import javax.servlet.ServletException;

+import javax.servlet.ServletRequest;

+import javax.servlet.ServletResponse;

+import javax.servlet.http.HttpServletRequest;

+import javax.servlet.http.HttpServletResponse;

+import org.apache.commons.logging.Log;

+import org.apache.commons.logging.LogFactory;

+import org.onap.aaf.cadi.Access;

+import org.onap.aaf.cadi.Access.Level;

+import org.onap.aaf.cadi.ServletContextAccess;

+import org.onap.aaf.cadi.util.Split;

+

+public class OTFApiEnforcementFilter implements Filter {

+  private static final Log log = LogFactory.getLog(OTFApiEnforcementFilter.class);

+  private String type;

+  private Map<String, List<String>> publicPaths;

+  private Access access = null;

+

+  public OTFApiEnforcementFilter(Access access, String enforce) throws ServletException {

+    this.access = access;

+    init(enforce);

+  }

+

+  @Override

+  public void init(FilterConfig fc) throws ServletException {

+    init(fc.getInitParameter("aaf_perm_type"));

+    // need the Context for Logging, instantiating ClassLoader, etc

+    ServletContextAccess sca = new ServletContextAccess(fc);

+    if (access == null) {

+      access = sca;

+    }

+  }

+

+  private void init(final String ptypes) throws ServletException {

+    if (Strings.isNullOrEmpty(ptypes)) {

+      throw new ServletException("OTFApiEnforcement requires aaf_perm_type property");

+    }

+    String[] full = Split.splitTrim(';', ptypes);

+    if (full.length <= 0) {

+      throw new ServletException("aaf_perm_type property is empty");

+    }

+

+    type = full[0];

+    publicPaths = new TreeMap<>();

+    if (full.length > 1) {

+      for (int i = 1; i < full.length; ++i) {

+        String[] pubArray = Split.split(':', full[i]);

+        if (pubArray.length == 2) {

+          List<String> ls = publicPaths.get(pubArray[0]);

+          if (ls == null) {

+            ls = new ArrayList<>();

+            publicPaths.put(pubArray[0], ls);

+          }

+          ls.add(pubArray[1]);

+        }

+      }

+    }

+  }

+

+  @Override

+  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain fc)

+      throws IOException, ServletException {

+    HttpServletRequest hreq = (HttpServletRequest) req;

+    final String meth = hreq.getMethod();

+    String path = hreq.getContextPath(); // + hreq.getPathInfo();

+

+    if (Strings.isNullOrEmpty(path) || "null".equals(path)) {

+      path = hreq.getRequestURI().substring(hreq.getContextPath().length());

+    }

+

+    List<String> list = publicPaths.get(meth);

+    if (list != null) {

+      for (String p : publicPaths.get(meth)) {

+        if (path.startsWith(p)) {

+          access.printf(

+              Level.INFO,

+              "%s accessed public API %s %s\n",

+              hreq.getUserPrincipal().getName(),

+              meth,

+              path);

+          fc.doFilter(req, resp);

+          return;

+        }

+      }

+    }

+    if (hreq.isUserInRole(type + '|' + path + '|' + meth)) {

+      access.printf(

+          Level.INFO,

+          "%s is allowed access to %s %s\n",

+          hreq.getUserPrincipal().getName(),

+          meth,

+          path);

+      fc.doFilter(req, resp);

+    } else {

+      access.printf(

+          Level.AUDIT,

+          "%s is denied access to %s %s\n",

+          hreq.getUserPrincipal().getName(),

+          meth,

+          path);

+      ((HttpServletResponse) resp).sendError(HttpServletResponse.SC_UNAUTHORIZED);

+    }

+  }

+

+  @Override

+  public void destroy() {}

+}

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
index 0000000..cd37067
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/config/OTFApiEnforcementFilterConfiguration.java
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.config;

+

+import javax.servlet.Filter;

+import javax.servlet.FilterConfig;

+import javax.servlet.ServletException;

+import org.onap.aaf.cadi.Access;

+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

+import org.springframework.boot.web.servlet.FilterRegistrationBean;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Conditional;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.context.annotation.PropertySource;

+

+@PropertySource("classpath:application.properties")

+@Configuration

+@ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true")

+public class OTFApiEnforcementFilterConfiguration {

+

+  private Access access;

+  private FilterConfig fc;

+

+  @Bean(name = "otfApiEnforcementFilterRegistrationBean")

+  @ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true")

+  public FilterRegistrationBean<Filter> otfApiEnforcementFilterRegistration()

+      throws ServletException {

+    FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();

+    initFilterParameters(registration);

+    registration.addUrlPatterns("/otf/api/testInstance/*", "/otf/api/testExecution/*", "/otf/api/testStrategy/*", "/otf/api/virtualTestHead/*");

+    registration.setFilter(otfApiEnforcementFilter());

+    registration.setName("otfApiEnforcementFilter");

+    registration.setOrder(1);

+    return registration;

+  }

+

+  @Bean(name = "otfApiEnforcementFilter")

+  @ConditionalOnProperty(prefix = "aaf",name ="enabled",havingValue = "true")

+  public Filter otfApiEnforcementFilter() throws ServletException {

+    return new OTFApiEnforcementFilter(access, System.getenv("AAF_PERM_TYPE"));

+  }

+

+  private void initFilterParameters(FilterRegistrationBean<Filter> registration) {

+    registration.addInitParameter("aaf_perm_type", System.getenv("AAF_PERM_TYPE"));

+  }

+}

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
index 0000000..c13caab
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/config/OTFLoggingFeature.java
@@ -0,0 +1,238 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.config;

+

+import org.glassfish.jersey.logging.LoggingFeature;

+import org.glassfish.jersey.message.MessageUtils;

+

+import javax.ws.rs.WebApplicationException;

+import javax.ws.rs.client.ClientRequestContext;

+import javax.ws.rs.client.ClientRequestFilter;

+import javax.ws.rs.client.ClientResponseContext;

+import javax.ws.rs.client.ClientResponseFilter;

+import javax.ws.rs.container.ContainerRequestContext;

+import javax.ws.rs.container.ContainerRequestFilter;

+import javax.ws.rs.container.ContainerResponseContext;

+import javax.ws.rs.container.ContainerResponseFilter;

+import javax.ws.rs.core.FeatureContext;

+import javax.ws.rs.core.MultivaluedMap;

+import javax.ws.rs.ext.WriterInterceptor;

+import javax.ws.rs.ext.WriterInterceptorContext;

+import java.io.*;

+import java.net.URI;

+import java.nio.charset.Charset;

+import java.util.ArrayList;

+import java.util.Base64;

+import java.util.List;

+import java.util.Objects;

+import java.util.logging.Level;

+import java.util.logging.Logger;

+

+public class OTFLoggingFeature extends LoggingFeature implements ContainerRequestFilter, ContainerResponseFilter,

+        ClientRequestFilter, ClientResponseFilter, WriterInterceptor {

+

+    private static final boolean printEntity = true;

+    private static final int maxEntitySize = 8 * 1024;

+    private final Logger logger = Logger.getLogger("OTFLoggingFeature");

+    private static final String ENTITY_LOGGER_PROPERTY = OTFLoggingFeature.class.getName();

+    private static final String NOTIFICATION_PREFIX = "* ";

+    private static final String REQUEST_PREFIX = "> ";

+    private static final String RESPONSE_PREFIX = "< ";

+    private static final String AUTHORIZATION = "Authorization";

+    private static final String EQUAL = " = ";

+    private static final String HEADERS_SEPARATOR = ", ";

+    private static List<String> requestHeaders;

+

+    static {

+        requestHeaders = new ArrayList<>();

+        requestHeaders.add(AUTHORIZATION);

+    }

+

+    public OTFLoggingFeature(Logger logger, Level level, Verbosity verbosity, Integer maxEntitySize) {

+        super(logger, level, verbosity, maxEntitySize);

+    }

+

+    @Override

+    public boolean configure(FeatureContext context) {

+        context.register(this);

+        return true;

+    }

+

+    private Object getEmail(Object authorization){

+        try{

+            String encoded = ((String) authorization).split(" ")[1];

+            String decoded =  new String(Base64.getDecoder().decode(encoded));

+            return decoded.split(":")[0];

+        }

+        catch (Exception e){

+            return authorization;

+        }

+    }

+

+    @Override

+    public void filter(final ClientRequestContext context) {

+        final StringBuilder b = new StringBuilder();

+        printHeaders(b, context.getStringHeaders());

+        printRequestLine(b, "Sending client request", context.getMethod(), context.getUri());

+

+        if (printEntity && context.hasEntity()) {

+            final OutputStream stream = new LoggingStream(b, context.getEntityStream());

+            context.setEntityStream(stream);

+            context.setProperty(ENTITY_LOGGER_PROPERTY, stream);

+            // not calling log(b) here - it will be called by the interceptor

+        } else {

+            log(b);

+        }

+    }

+

+    @Override

+    public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {

+        final StringBuilder b = new StringBuilder();

+        printResponseLine(b, "Client response received", responseContext.getStatus());

+

+        if (printEntity && responseContext.hasEntity()) {

+            responseContext.setEntityStream(logInboundEntity(b, responseContext.getEntityStream(),

+                    MessageUtils.getCharset(responseContext.getMediaType())));

+        }

+        log(b);

+    }

+

+    @Override

+    public void filter(final ContainerRequestContext context) throws IOException {

+        final StringBuilder b = new StringBuilder();

+        printHeaders(b, context.getHeaders());

+        printRequestLine(b, "Server has received a request", context.getMethod(), context.getUriInfo().getRequestUri());

+

+        if (printEntity && context.hasEntity()) {

+            context.setEntityStream(logInboundEntity(b, context.getEntityStream(), MessageUtils.getCharset(context.getMediaType())));

+        }

+        log(b);

+    }

+

+    @Override

+    public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext) {

+        final StringBuilder b = new StringBuilder();

+        printResponseLine(b, "Server responded with a response", responseContext.getStatus());

+

+        if (printEntity && responseContext.hasEntity()) {

+            final OutputStream stream = new LoggingStream(b, responseContext.getEntityStream());

+            responseContext.setEntityStream(stream);

+            requestContext.setProperty(ENTITY_LOGGER_PROPERTY, stream);

+            // not calling log(b) here - it will be called by the interceptor

+        } else {

+            log(b);

+        }

+    }

+

+    @Override

+    public void aroundWriteTo(final WriterInterceptorContext writerInterceptorContext) throws IOException, WebApplicationException {

+        final LoggingStream stream = (LoggingStream) writerInterceptorContext.getProperty(ENTITY_LOGGER_PROPERTY);

+        writerInterceptorContext.proceed();

+        if (stream != null) {

+            log(stream.getStringBuilder(MessageUtils.getCharset(writerInterceptorContext.getMediaType())));

+        }

+    }

+

+    private static class LoggingStream extends FilterOutputStream {

+        private final StringBuilder b;

+        private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

+

+        LoggingStream(final StringBuilder b, final OutputStream inner) {

+            super(inner);

+

+            this.b = b;

+        }

+

+        StringBuilder getStringBuilder(Charset charset) {

+            // write entity to the builder

+            final byte[] entity = byteArrayOutputStream.toByteArray();

+

+            b.append(new String(entity, 0, Math.min(entity.length, maxEntitySize), charset));

+            if (entity.length > maxEntitySize) {

+                b.append("...more...");

+            }

+            b.append('\n');

+

+            return b;

+        }

+

+        public void write(final int i) throws IOException {

+            if (byteArrayOutputStream.size() <= maxEntitySize) {

+                byteArrayOutputStream.write(i);

+            }

+            out.write(i);

+        }

+    }

+

+    private void printHeaders(StringBuilder b, MultivaluedMap<String, String> headers) {

+        for (String header : requestHeaders) {

+            if (Objects.nonNull(headers.get(header))) {

+                if(header.equalsIgnoreCase("Authorization")){

+                    b.append(header).append(EQUAL).append(getEmail(headers.get(header).get(0))).append(HEADERS_SEPARATOR);

+                }

+                else{

+                    b.append(header).append(EQUAL).append(headers.get(header)).append(HEADERS_SEPARATOR);

+                }

+            }

+        }

+        int lastIndex = b.lastIndexOf(HEADERS_SEPARATOR);

+        if (lastIndex != -1) {

+            b.delete(lastIndex, lastIndex + HEADERS_SEPARATOR.length());

+            b.append("\n");

+        }

+    }

+

+    private void log(final StringBuilder b) {

+        String message = b.toString();

+        if (logger != null) {

+            logger.info(message);

+        }

+    }

+

+    private void printRequestLine(final StringBuilder b, final String note, final String method, final URI uri) {

+        b.append(NOTIFICATION_PREFIX)

+                .append(note)

+                .append(" on thread ").append(Thread.currentThread().getId())

+                .append(REQUEST_PREFIX).append(method).append(" ")

+                .append(uri.toASCIIString()).append("\n");

+    }

+

+    private void printResponseLine(final StringBuilder b, final String note, final int status) {

+        b.append(NOTIFICATION_PREFIX)

+                .append(note)

+                .append(" on thread ").append(Thread.currentThread().getId())

+                .append(RESPONSE_PREFIX)

+                .append(Integer.toString(status))

+                .append("\n");

+    }

+

+    private InputStream logInboundEntity(final StringBuilder b, InputStream stream, final Charset charset) throws IOException {

+        if (!stream.markSupported()) {

+            stream = new BufferedInputStream(stream);

+        }

+        stream.mark(maxEntitySize + 1);

+        final byte[] entity = new byte[maxEntitySize + 1];

+        final int entitySize = stream.read(entity);

+        b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));

+        if (entitySize > maxEntitySize) {

+            b.append("...more...");

+        }

+        b.append('\n');

+        stream.reset();

+        return stream;

+    }

+}
\ 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
index 0000000..7132a88
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/exception/TestHeadNotFoundException.java
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.exception;

+

+public class TestHeadNotFoundException extends Exception {

+  private static final long serialVersionUID = 1L;

+

+  public TestHeadNotFoundException(String message) {

+    super(message);

+  }

+

+  public TestHeadNotFoundException(Throwable cause) {

+    super(cause);

+  }

+

+  public TestHeadNotFoundException(String message, Throwable cause) {

+    super(message, cause);

+  }

+}

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
index 0000000..2029f5c
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/exception/TestParametersException.java
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.exception;

+

+public class TestParametersException extends Exception {

+  private static final long serialVersionUID = 1L;

+

+  public TestParametersException(String message) {

+    super(message);

+  }

+

+  public TestParametersException(Throwable cause) {

+    super(cause);

+  }

+

+  public TestParametersException(String message, Throwable cause) {

+    super(message, cause);

+  }

+}

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
index 0000000..d319bcb
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/exception/UserNotFoundException.java
@@ -0,0 +1,33 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.exception;

+

+public class UserNotFoundException extends Exception {

+  private static final long serialVersionUID = 1L;

+

+  public UserNotFoundException(String message) {

+    super(message);

+  }

+

+  public UserNotFoundException(Throwable cause) {

+    super(cause);

+  }

+

+  public UserNotFoundException(String message, Throwable cause) {

+    super(message, cause);

+  }

+}

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
index 0000000..25c06b3
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/handler/CamundaProcessDeploymentHandler.java
@@ -0,0 +1,131 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.handler;

+

+import org.oran.otf.api.Utilities;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import java.io.InputStream;

+import java.util.Base64;

+import javax.ws.rs.core.Response;

+import org.apache.http.HttpEntity;

+import org.apache.http.client.ClientProtocolException;

+import org.apache.http.client.ResponseHandler;

+import org.apache.http.client.methods.HttpUriRequest;

+import org.apache.http.client.methods.RequestBuilder;

+import org.apache.http.conn.HttpHostConnectException;

+import org.apache.http.conn.ssl.NoopHostnameVerifier;

+import org.apache.http.entity.ContentType;

+import org.apache.http.entity.mime.MultipartEntityBuilder;

+import org.apache.http.impl.client.CloseableHttpClient;

+import org.apache.http.impl.client.HttpClients;

+import org.apache.http.util.EntityUtils;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.stereotype.Component;

+

+@Component

+public class CamundaProcessDeploymentHandler {

+  private static final Logger logger =

+      LoggerFactory.getLogger(CamundaProcessDeploymentHandler.class);

+

+  private CamundaProcessDeploymentHandler() {

+    // prevent instantiation

+  }

+

+  public Response start(InputStream bpmn, InputStream compressedResources) {

+    // Read necessary environment variables - Avoiding using Spring dependencies (@Value)

+    String host = System.getenv("otf.camunda.host");

+    String path = System.getenv("otf.camunda.uri.deploy-test-strategy-zip");

+    int port = Utilities.TryGetEnvironmentVariable("otf.camunda.port");

+    String aafCredentialsDecoded =

+        System.getenv("AAF_ID") + ":" + System.getenv("AAF_MECH_PASSWORD");

+

+    if (!Utilities.isHostValid(host)) {

+      logger.error("Host (%s) must use either the http or https protocol.", host);

+      return null;

+    }

+

+    if (!Utilities.isPortValid(port)) {

+      logger.error(

+          "Invalid port (%s) specified as environment variable 'otf.camunda.port'.",

+          System.getenv("otf.camunda.port"));

+      return null;

+    }

+

+    // Form the full url

+    String postUrl = String.format("%s:%s/%s", host, port, path);

+

+    try (CloseableHttpClient httpclient =

+        HttpClients.custom().setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE).build()) {

+

+      // build multipart upload request

+      MultipartEntityBuilder builder =

+          MultipartEntityBuilder.create()

+              .addBinaryBody("bpmn", bpmn, ContentType.DEFAULT_BINARY, "bpmn");

+

+      // add resources to the request if they were supplied

+      if (compressedResources != null) {

+        builder.addBinaryBody(

+            "resources", compressedResources, ContentType.DEFAULT_BINARY, "resources");

+      }

+

+      HttpEntity data = builder.build();

+

+      // build http request and assign multipart upload data

+      HttpUriRequest request =

+          RequestBuilder.post(postUrl)

+              .addHeader(

+                  "Authorization",

+                  "Basic " + Base64.getEncoder().encodeToString(aafCredentialsDecoded.getBytes()))

+              .setEntity(data)

+              .build();

+

+      System.out.println("Executing request " + request.getRequestLine());

+

+      // Create a custom response handler

+      ResponseHandler<Response> responseHandler =

+          response -> {

+            int status = response.getStatusLine().getStatusCode();

+            if (status >= 200 && status < 300) {

+              HttpEntity entity = response.getEntity();

+              String message = entity != null ? EntityUtils.toString(entity) : null;

+              return Response.ok(message).build();

+            } else if (status == 400) {

+              HttpEntity entity = response.getEntity();

+              String message =

+                  entity != null

+                      ? EntityUtils.toString(entity)

+                      : "Supplied bpmn file is not deployable.";

+              return Utilities.Http.BuildResponse.badRequestWithMessage(message);

+            } else {

+              throw new ClientProtocolException("Unexpected response status: " + status);

+            }

+          };

+

+      Response responseBody = httpclient.execute(request, responseHandler);

+      System.out.println("----------------------------------------");

+      System.out.println(responseBody.getEntity().toString());

+

+      return responseBody;

+    } catch (HttpHostConnectException e) {

+      return ResponseUtility.Build.serviceUnavailableWithMessage(e.getMessage());

+    } catch (Exception e) {

+      e.printStackTrace();

+      return ResponseUtility.Build.internalServerErrorWithMessage("Unable to deploy definition.");

+    }

+  }

+}

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
index 0000000..00e26d7
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/handler/CamundaProcessExecutionHandler.java
@@ -0,0 +1,105 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.handler;

+

+import org.oran.otf.api.Utilities;

+import org.oran.otf.api.Utilities.LogLevel;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.model.local.OTFApiResponse;

+import org.oran.otf.common.model.local.WorkflowRequest;

+import org.oran.otf.common.utility.gson.Convert;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import com.fasterxml.jackson.core.type.TypeReference;

+import com.fasterxml.jackson.databind.ObjectMapper;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+import org.apache.http.HttpEntity;

+import org.apache.http.HttpResponse;

+import org.apache.http.conn.HttpHostConnectException;

+import org.apache.http.util.EntityUtils;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.stereotype.Component;

+

+@Component

+public class CamundaProcessExecutionHandler {

+  private static final Logger logger =

+      LoggerFactory.getLogger(CamundaProcessExecutionHandler.class);

+

+  private CamundaProcessExecutionHandler() {

+    // prevent instantiation

+  }

+

+  public Response startProcessInstance(WorkflowRequest request) throws Exception {

+    try {

+      //      if (!Utilities.Camunda.isCamundaOnline()) {

+      //        Utilities.Http.BuildResponse.internalServerErrorWithMessage(

+      //            "Unable to start process instance because the test control unit is

+      // unavailable.");

+      //      }

+

+      // Read necessary environment variables - Avoiding using Spring dependencies (@Value)

+      String host = System.getenv("otf.camunda.host");

+      String path = System.getenv("otf.camunda.uri.execute-test");

+      int port = Utilities.TryGetEnvironmentVariable("otf.camunda.port");

+

+      if (!Utilities.isHostValid(host)) {

+        logger.error(String.format("Host (%s) must use either the http or https protocol.", host));

+        return null;

+      }

+

+      if (!Utilities.isPortValid(port)) {

+        logger.error(

+            String.format(

+                "Invalid port (%s) specified as environment variable 'otf.camunda.port'.",

+                System.getenv("otf.camunda.port")));

+        return null;

+      }

+

+      // Form the URL

+      String postUrl = String.format("%s:%s/%s", host, port, path);

+

+      // Send and store the response

+      HttpResponse response = Utilities.Http.httpPostJsonUsingAAF(postUrl, request.toString());

+      // Get the entity and attempt to convert it to a TestExecution object.

+      HttpEntity entity = response.getEntity();

+      String rawEntity = EntityUtils.toString(entity);

+      ObjectMapper mapper = new ObjectMapper();

+      OTFApiResponse otfApiResponse = mapper.readValue(rawEntity, OTFApiResponse.class);

+

+      if (otfApiResponse.getStatusCode() == 400) {

+        return Response.status(400)

+            .type(MediaType.APPLICATION_JSON_TYPE)

+            .entity(otfApiResponse.toString())

+            .build();

+      }

+

+      String jsonMessage = otfApiResponse.getMessage();

+      TestExecution testExecution =

+          Convert.jsonToObject(jsonMessage, new TypeReference<TestExecution>() {});

+      return Response.status(otfApiResponse.getStatusCode())

+          .entity(testExecution.toString())

+          .build();

+

+    } catch (HttpHostConnectException e) {

+      return ResponseUtility.Build.serviceUnavailableWithMessage(e.getMessage());

+    } catch (Exception e) {

+      Utilities.printStackTrace(e, LogLevel.ERROR);

+      return ResponseUtility.Build.internalServerError();

+    }

+  }

+}

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
index 0000000..4bd2378
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/HealthService.java
@@ -0,0 +1,54 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service;

+

+import org.oran.otf.common.model.local.OTFApiResponse;

+import io.swagger.annotations.Api;

+import io.swagger.v3.oas.annotations.Operation;

+import io.swagger.v3.oas.annotations.media.Content;

+import io.swagger.v3.oas.annotations.media.Schema;

+import io.swagger.v3.oas.annotations.responses.ApiResponse;

+import io.swagger.v3.oas.annotations.tags.Tag;

+

+import javax.ws.rs.GET;

+import javax.ws.rs.Path;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+

+@Api

+@Path("/health")

+@Tag(name = "Health Service", description = "Query the availability of the API")

+@Produces({MediaType.APPLICATION_JSON})

+public interface HealthService {

+

+  @GET

+  @Path("/v1")

+  @Produces({MediaType.APPLICATION_JSON})

+  @Operation(

+      summary = "Checks if the test control unit is available",

+      responses = {

+        @ApiResponse(

+            responseCode = "200",

+            description = "The test control unit is available",

+            content =

+                @Content(

+                    mediaType = "application/json",

+                    schema = @Schema(implementation = OTFApiResponse.class)))

+      })

+  Response getHealth();

+}

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
index 0000000..ec32e47
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/OtfOpenService.java
@@ -0,0 +1,35 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service;

+

+import io.swagger.v3.oas.annotations.Hidden;

+import javax.ws.rs.GET;

+import javax.ws.rs.Path;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+import org.springframework.stereotype.Service;

+

+@Hidden

+@Path("/")

+public interface OtfOpenService {

+  @GET

+  @Hidden

+  @Produces(MediaType.APPLICATION_JSON)

+  @Path("/demo/openapi.json")

+  Response convertSwaggerFile();

+}

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
index 0000000..b1d5d5e
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/TestExecutionService.java
@@ -0,0 +1,92 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service;

+

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.model.local.OTFApiResponse;

+import io.swagger.annotations.Api;

+import io.swagger.v3.oas.annotations.Operation;

+import io.swagger.v3.oas.annotations.media.Content;

+import io.swagger.v3.oas.annotations.media.Schema;

+import io.swagger.v3.oas.annotations.responses.ApiResponse;

+import io.swagger.v3.oas.annotations.tags.Tag;

+import javax.ws.rs.GET;

+import javax.ws.rs.HeaderParam;

+import javax.ws.rs.Path;

+import javax.ws.rs.PathParam;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+

+import org.springframework.stereotype.Component;

+

+@Component

+@Api

+@Path("/testExecution")

+@Tag(name = "Test Services", description = "")

+@Produces(MediaType.APPLICATION_JSON)

+public interface TestExecutionService {

+  @GET

+  @Path("v1/status/executionId/{executionId}")

+  @Produces({MediaType.APPLICATION_JSON})

+  @Operation(

+      description = "Respond with a test execution object if it exists",

+      summary = "Find test execution log by processInstanceId",

+      responses = {

+        @ApiResponse(

+            responseCode = "200",

+            description = "The created Test Instance object is returned when it is created",

+            content = {

+              @Content(

+                  mediaType = "application/json",

+                  schema = @Schema(implementation = TestExecution.class))

+            })

+      })

+  Response getExecutionStatus(

+      @HeaderParam("Authorization") String authorization,

+      @PathParam("executionId") String executionId);

+

+  @GET

+  @Path("v1/executionId/{executionId}")

+  @Produces({MediaType.APPLICATION_JSON})

+  @Operation(

+      description =

+          "Respond with a test execution object, and state of the process instance if it exists.",

+      summary = "Find test execution log by executionId",

+      responses = {

+          @ApiResponse(

+              responseCode = "200",

+              description = "The created Test Instance object is returned when it is created",

+              content = {

+                  @Content(

+                      mediaType = "application/json",

+                      schema = @Schema(implementation = TestExecution.class))

+              }),

+          @ApiResponse(

+              responseCode = "404",

+              description =

+                  "No process instance was found with the executionId used to query the service",

+              content = {

+                  @Content(

+                      mediaType = "application/json",

+                      schema = @Schema(implementation = OTFApiResponse.class))

+              })

+      })

+  Response getExecution(

+      @HeaderParam("Authorization") String authorization,

+      @PathParam("executionId") String executionId);

+}

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
index 0000000..6b60801
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/TestInstanceService.java
@@ -0,0 +1,374 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service;

+

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.model.TestInstance;

+import org.oran.otf.common.model.local.OTFApiResponse;

+import org.oran.otf.common.model.local.TestInstanceCreateRequest;

+import org.oran.otf.common.model.local.WorkflowRequest;

+import io.swagger.v3.oas.annotations.Operation;

+import io.swagger.v3.oas.annotations.Parameter;

+import io.swagger.v3.oas.annotations.media.ArraySchema;

+import io.swagger.v3.oas.annotations.media.Content;

+import io.swagger.v3.oas.annotations.media.Schema;

+import io.swagger.v3.oas.annotations.responses.ApiResponse;

+import io.swagger.v3.oas.annotations.tags.Tag;

+import javax.ws.rs.Consumes;

+import javax.ws.rs.GET;

+import javax.ws.rs.HeaderParam;

+import javax.ws.rs.POST;

+import javax.ws.rs.Path;

+import javax.ws.rs.PathParam;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+

+import org.springframework.stereotype.Component;

+

+@Component

+@Path("/testInstance")

+@Tag(name = "Test Services", description = "")

+@Produces(MediaType.APPLICATION_JSON)

+public interface TestInstanceService {

+  @POST

+  @Path("/execute/v1/id/{testInstanceId}")

+  @Operation(

+      description =

+          "Execute a test instance by it's unique identifier. Test instances can be executed"

+              + " either both synchronously and asynchronously.",

+      summary = "Execute test instance by id",

+      responses = {

+        @ApiResponse(

+            responseCode = "200",

+            description =

+                "A successful synchronously executed test returns a test execution object",

+            content =

+                @Content(

+                    mediaType = "application/json",

+                    schema = @Schema(implementation = TestExecution.class))),

+        @ApiResponse(

+            responseCode = "201",

+            description =

+                "A successful asynchronously executed test instance returns a base test execution.",

+            content =

+                @Content(

+                    mediaType = "application/json",

+                    schema = @Schema(implementation = TestExecution.class))),

+        @ApiResponse(

+            responseCode = "401",

+            description =

+                "The mechanized identifier used with the request is prohibited from accessing the resource.",

+            content = {

+              @Content(

+                  mediaType = "application/json",

+                  schema = @Schema(implementation = OTFApiResponse.class))

+            })

+      })

+  @Consumes(MediaType.APPLICATION_JSON)

+  @Produces(MediaType.APPLICATION_JSON)

+  Response execute(

+      @Parameter(

+              allowEmptyValue = false,

+              description = "A string representation of a BSON ObjectId",

+              example = "12345678912345678912345f",

+              required = true,

+              schema =

+                  @Schema(

+                      type = "string",

+                      format = "objectid",

+                      description = "The UUID of the test instance"))

+          @PathParam("testInstanceId")

+          String testInstanceId,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "Base64 encoded Application Authorization Framework credentials",

+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",

+              required = true)

+          @HeaderParam("Authorization")

+          String authorization,

+      WorkflowRequest request);

+

+  @POST

+  @Operation(

+      description = "Create a test instance using the latest version of the test definition.",

+      summary = "Create test instance by test definition id",

+      responses = {

+        @ApiResponse(

+            responseCode = "201",

+            description = "The created Test Instance object is returned when it is created",

+            content = {

+              @Content(

+                  mediaType = "application/json",

+                  schema = @Schema(implementation = TestInstance.class))

+            })

+      })

+  @Consumes(MediaType.APPLICATION_JSON)

+  @Produces(MediaType.APPLICATION_JSON)

+  @Path("/create/v1/testDefinitionId/{testDefinitionId}")

+  Response createByTestDefinitionId(

+      @Parameter(

+              allowEmptyValue = false,

+              description = "A string representation of a BSON ObjectId",

+              example = "12345678912345678912345f",

+              required = true,

+              schema =

+                  @Schema(

+                      type = "string",

+                      format = "uuid",

+                      description = "The UUID of the test definition"))

+          @PathParam("testDefinitionId")

+          String testDefinitionId,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "Base64 encoded Application Authorization Framework credentials",

+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",

+              required = true)

+          @HeaderParam("Authorization")

+          String authorization,

+      TestInstanceCreateRequest request);

+

+  @POST

+  @Operation(

+      description = "Create a test instance using the specified version of the test definition",

+      summary = "Create test instance by test definition id and version",

+      responses = {

+        @ApiResponse(

+            responseCode = "201",

+            description = "The created Test Instance object is returned when it is created",

+            content = {

+              @Content(

+                  mediaType = "application/json",

+                  schema = @Schema(implementation = TestInstance.class))

+            })

+      })

+  @Consumes(MediaType.APPLICATION_JSON)

+  @Produces(MediaType.APPLICATION_JSON)

+  @Path("/create/v1/testDefinitionId/{testDefinitionId}/version/{version}")

+  Response createByTestDefinitionId(

+      @Parameter(

+              allowEmptyValue = false,

+              description = "A string representation of a BSON ObjectId",

+              example = "12345678912345678912345f",

+              required = true,

+              schema =

+                  @Schema(

+                      type = "string",

+                      format = "uuid",

+                      description = "The UUID of the test definition."))

+          @PathParam("testDefinitionId")

+          String testDefinitionId,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "The version of the test definition used to create the instance",

+              example = "2",

+              required = true)

+          @PathParam("version")

+          int version,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "Base64 encoded Application Authorization Framework credentials",

+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",

+              required = true)

+          @HeaderParam("Authorization")

+          String authorization,

+      TestInstanceCreateRequest request);

+

+  @POST

+  @Operation(

+      description = "Create a test instance using the latest version of the test definition",

+      summary = "Create test instance by process definition key",

+      responses = {

+        @ApiResponse(

+            responseCode = "201",

+            description = "The created Test Instance object is returned when it is created",

+            content = {

+              @Content(

+                  mediaType = "application/json",

+                  schema = @Schema(implementation = TestInstance.class))

+            })

+      })

+  @Consumes(MediaType.APPLICATION_JSON)

+  @Produces(MediaType.APPLICATION_JSON)

+  @Path("/create/v1/processDefinitionKey/{processDefinitionKey}")

+  Response createByProcessDefinitionKey(

+      @Parameter(

+              allowEmptyValue = false,

+              description = "The process definition key associated with the test definition",

+              example = "someUniqueProcessDefinitionKey",

+              required = true)

+          @PathParam("processDefinitionKey")

+          String processDefinitionKey,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "Base64 encoded Application Authorization Framework credentials",

+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",

+              required = true)

+          @HeaderParam("Authorization")

+          String authorization,

+      TestInstanceCreateRequest request);

+

+  @POST

+  @Operation(

+      description = "Create a test instance using the unique process definition key and version",

+      summary = "Create test instance by process definition key and version",

+      responses = {

+        @ApiResponse(

+            responseCode = "201",

+            description = "The created Test Instance object is returned when it is created",

+            content = {

+              @Content(

+                  mediaType = "application/json",

+                  schema = @Schema(implementation = TestInstance.class))

+            })

+      })

+  @Consumes(MediaType.APPLICATION_JSON)

+  @Produces(MediaType.APPLICATION_JSON)

+  @Path("/create/v1/processDefinitionKey/{processDefinitionKey}/version/{version}")

+  Response createByProcessDefinitionKey(

+      @Parameter(

+              allowEmptyValue = false,

+              description = "The process definition key associated with the test definition",

+              example = "someUniqueProcessDefinitionKey",

+              required = true)

+          @PathParam("processDefinitionKey")

+          String processDefinitionKey,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "The version of the test definition used to create the instance",

+              example = "2",

+              required = true)

+          @PathParam("version")

+          int version,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "Base64 encoded Application Authorization Framework credentials",

+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",

+              required = true)

+          @HeaderParam("Authorization")

+          String authorization,

+      TestInstanceCreateRequest request);

+

+  @GET

+  @Operation(

+      description = "Finds a test instance by it's unique identifier",

+      summary = "Find test instance by id",

+      responses = {

+        @ApiResponse(

+            responseCode = "200",

+            description = "A Test Instance object is returned if it exists",

+            content = {

+              @Content(

+                  mediaType = "application/json",

+                  schema = @Schema(implementation = TestInstance.class))

+            })

+      })

+  @Produces(MediaType.APPLICATION_JSON)

+  @Path("/v1/id/{id}")

+  Response findById(

+      @Parameter(

+              allowEmptyValue = false,

+              description = "A string representation of a BSON ObjectId",

+              example = "12345678912345678912345f",

+              required = true,

+              schema =

+                  @Schema(

+                      type = "string",

+                      format = "uuid",

+                      description = "The UUID of the test instance"))

+          @PathParam("id")

+          String testInstanceId,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "Base64 encoded Application Authorization Framework credentials",

+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",

+              required = true)

+          @HeaderParam("Authorization")

+          String authorization);

+

+  @GET

+  @Operation(

+      description = "Finds all test instance belonging to the unique process definition key",

+      summary = "Find test instance(s) by process definition key",

+      responses = {

+        @ApiResponse(

+            responseCode = "200",

+            description = "An array of found Test Instance objects are returned",

+            content = {

+              @Content(

+                  array = @ArraySchema(schema = @Schema(implementation = TestInstance.class)),

+                  mediaType = "application/json")

+            })

+      })

+  @Produces(MediaType.APPLICATION_JSON)

+  @Path("/v1/processDefinitionKey/{processDefinitionKey}")

+  Response findByProcessDefinitionKey(

+      @Parameter(

+              allowEmptyValue = false,

+              description = "The process definition key associated with the test definition",

+              example = "someUniqueProcessDefinitionKey",

+              required = true)

+          @PathParam("processDefinitionKey")

+          String processDefinitionKey,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "Base64 encoded Application Authorization Framework credentials",

+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",

+              required = true)

+          @HeaderParam("Authorization")

+          String authorization);

+

+  @GET

+  @Operation(

+      description =

+          "Finds all test instance belonging to the unique process definition key and version",

+      summary = "Find test instance(s) by process definition key and version",

+      responses = {

+        @ApiResponse(

+            responseCode = "200",

+            description = "An array of found Test Instance objects are returned",

+            content = {

+              @Content(

+                  array = @ArraySchema(schema = @Schema(implementation = TestInstance.class)),

+                  mediaType = "application/json")

+            })

+      })

+  @Produces(MediaType.APPLICATION_JSON)

+  @Path("/v1/processDefinitionKey/{processDefinitionKey}/version/{version}")

+  Response findByProcessDefinitionKeyAndVersion(

+      @Parameter(

+              allowEmptyValue = false,

+              description = "The process definition key associated with the test definition",

+              example = "someUniqueProcessDefinitionKey",

+              required = true)

+          @PathParam("processDefinitionKey")

+          String processDefinitionKey,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "The version of the test definition used to create the instance",

+              example = "2",

+              required = true)

+          @PathParam("version")

+          String version,

+      @Parameter(

+              allowEmptyValue = false,

+              description = "Base64 encoded Application Authorization Framework credentials",

+              example = "Basic b3RmQGF0dC5jb206cGFzc3dvcmQxMjM=",

+              required = true)

+          @HeaderParam("Authorization")

+          String authorization);

+}

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
index 0000000..84b2535
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/TestStrategyService.java
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service;

+

+import io.swagger.annotations.Api;

+import io.swagger.v3.oas.annotations.Hidden;

+import io.swagger.v3.oas.annotations.tags.Tag;

+import java.io.InputStream;

+import javax.ws.rs.Consumes;

+import javax.ws.rs.DELETE;

+import javax.ws.rs.HeaderParam;

+import javax.ws.rs.POST;

+import javax.ws.rs.Path;

+import javax.ws.rs.PathParam;

+import javax.ws.rs.Produces;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+import org.glassfish.jersey.media.multipart.FormDataParam;

+

+@Api

+@Hidden

+@Path("/testStrategy")

+@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)")

+@Produces({MediaType.APPLICATION_JSON})

+public interface TestStrategyService {

+  @POST

+  @Hidden

+  @Path("/deploy/v1")

+  @Consumes(MediaType.MULTIPART_FORM_DATA)

+  @Produces(MediaType.APPLICATION_JSON)

+  Response deployTestStrategy(

+      @FormDataParam("bpmn") InputStream bpmn,

+      @FormDataParam("resources") InputStream compressedResources,

+      @FormDataParam("testDefinitionId") String testDefinitionId,

+      @FormDataParam("testDefinitionDeployerId") String testDefinitionDeployerId,

+      @FormDataParam("definitionId") String definitionId,

+      @HeaderParam("Authorization") String authorization);

+

+  @DELETE

+  @Hidden

+  @Path("/delete/v1/testDefinitionId/{testDefinitionId}")

+  Response deleteByTestDefinitionId(

+      @PathParam("testDefinitionId") String testDefinitionId,

+      @HeaderParam("authorization") String authorization);

+

+  @DELETE

+  @Hidden

+  @Path("/delete/v1/deploymentId/{deploymentId}")

+  Response deleteByDeploymentId(

+      @PathParam("deploymentId") String deploymentId,

+      @HeaderParam("authorization") String authorization);

+}

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
index 0000000..9c6ed6f
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/VirtualTestHeadService.java
@@ -0,0 +1,55 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service;

+

+import org.oran.otf.common.model.TestHead;

+import org.oran.otf.common.model.local.OTFApiResponse;

+import io.swagger.annotations.Api;

+import io.swagger.v3.oas.annotations.Operation;

+import io.swagger.v3.oas.annotations.media.Content;

+import io.swagger.v3.oas.annotations.media.Schema;

+import io.swagger.v3.oas.annotations.responses.ApiResponse;

+import io.swagger.v3.oas.annotations.tags.Tag;

+

+import javax.ws.rs.*;

+import javax.ws.rs.core.HttpHeaders;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+

+

+@Api

+@Path("/virtualTestHead")

+@Tag(name = "Health Service", description = "Query the availability of the API")

+@Produces({MediaType.APPLICATION_JSON})

+public interface VirtualTestHeadService {

+

+    @PATCH

+    @Path("/v1/{testHeadName}")

+    @Produces({MediaType.APPLICATION_JSON})

+    @Operation(

+            summary = "Used to update fields in the virtual test head",

+            responses = {

+                    @ApiResponse(

+                            responseCode = "200",

+                            description = "The response will include the new vth object",

+                            content =

+                            @Content(

+                                    mediaType = "application/json",

+                                    schema = @Schema(implementation = OTFApiResponse.class)))

+            })

+    Response updateVirtualTestHead(@HeaderParam(HttpHeaders.AUTHORIZATION) String authorization, @PathParam("testHeadName") String testHeadName, TestHead newTestHead);

+}

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
index 0000000..ed4755a
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/HealthServiceImpl.java
@@ -0,0 +1,34 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service.impl;

+

+import org.oran.otf.api.service.HealthService;

+import org.oran.otf.common.model.local.OTFApiResponse;

+import javax.ws.rs.core.Response;

+

+import org.springframework.stereotype.Service;

+

+@Service

+public class HealthServiceImpl implements HealthService {

+

+  public HealthServiceImpl() {}

+

+  @Override

+  public Response getHealth() {

+    return Response.ok(new OTFApiResponse(200, "UP")).build();

+  }

+}

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
index 0000000..bfff6bb
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/OtfOpenServiceImpl.java
@@ -0,0 +1,49 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service.impl;

+

+import org.oran.otf.api.Utilities;

+import org.oran.otf.api.Utilities.LogLevel;

+import org.oran.otf.api.service.OtfOpenService;

+import com.google.gson.JsonObject;

+import com.google.gson.JsonParser;

+import javax.ws.rs.core.Response;

+import org.apache.http.HttpResponse;

+import org.apache.http.util.EntityUtils;

+import org.springframework.stereotype.Service;

+

+@Service

+public class OtfOpenServiceImpl implements OtfOpenService {

+

+  @Override

+  public Response convertSwaggerFile() {

+    try {

+      HttpResponse res =

+          Utilities.Http.httpGetUsingAAF("https://localhost:8443/otf/api/openapi.json");

+      String resStr = EntityUtils.toString(res.getEntity());

+      JsonObject resJson = new JsonParser().parse(resStr).getAsJsonObject();

+      if (resJson.has("openapi")) {

+        resJson.addProperty("openapi", "3.0.0");

+        return Response.ok(resJson.toString()).build();

+      }

+    } catch (Exception e) {

+      Utilities.printStackTrace(e, LogLevel.ERROR);

+    }

+

+    return Utilities.Http.BuildResponse.internalServerError();

+  }

+}

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
index 0000000..e758c6b
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestExecutionServiceImpl.java
@@ -0,0 +1,160 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service.impl;

+

+import org.oran.otf.api.Utilities;

+import org.oran.otf.api.service.TestExecutionService;

+import org.oran.otf.common.model.Group;

+import org.oran.otf.common.model.TestExecution;

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.repository.GroupRepository;

+import org.oran.otf.common.repository.TestExecutionRepository;

+import org.oran.otf.common.repository.UserRepository;

+import org.oran.otf.common.utility.permissions.PermissionChecker;

+import org.oran.otf.common.utility.permissions.UserPermission;

+import com.google.gson.JsonElement;

+import com.google.gson.JsonObject;

+import com.google.gson.JsonParser;

+import java.util.Optional;

+import java.util.UUID;

+import javax.ws.rs.core.Response;

+

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Service;

+

+@Service

+public class TestExecutionServiceImpl implements TestExecutionService {

+  private static final Logger logger = LoggerFactory.getLogger(TestExecutionServiceImpl.class);

+  @Autowired

+  private UserRepository userRepository;

+  @Autowired private TestExecutionRepository testExecutionRepository;

+  @Autowired private GroupRepository groupRepository;

+

+  @Override

+  public Response getExecutionStatus(String authorization, String executionId) {

+    if (authorization == null) {

+      return Utilities.Http.BuildResponse.unauthorizedWithMessage("Missing authorization header.");

+    }

+    // check if the executionId is a valid UUID

+    try {

+      UUID.fromString(executionId);

+    } catch (IllegalArgumentException e) {

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          "Invalid execution identifier. Expected type is UUID (v4).");

+    }

+

+    // try to find the test execution

+    Optional<TestExecution> optionalTestExecution =

+        testExecutionRepository.findFirstByProcessInstanceId(executionId);

+    TestExecution testExecution = Utilities.resolveOptional(optionalTestExecution);

+    if (testExecution == null) {

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          String.format("An execution with identifier %s was not found.", executionId));

+    }

+

+    // try to find the group of the test execution

+    String testExecutionGroupId = testExecution.getGroupId().toString();

+    Optional<Group> optionalGroup = groupRepository.findById(testExecutionGroupId);

+    Group group = Utilities.resolveOptional(optionalGroup);

+    if (group == null) {

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          String.format(

+              "The group (id: %s) associated with the test execution does not exist.",

+              testExecutionGroupId));

+    }

+

+    // try to find the user for the mechanizedId used to make this request

+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);

+    if (user == null) {

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          "No user associated with mechanized identifier used for this request.");

+    }

+    // if it doesnt have read permission then return bad request

+    if (!PermissionChecker.hasPermissionTo(user,group, UserPermission.Permission.READ,groupRepository)){

+      return Utilities.Http.BuildResponse.unauthorizedWithMessage(

+          "Unauthorized to view this test execution.");

+    }

+    // Used the build the final response to be returned

+    JsonObject res = new JsonObject();

+    try {

+      // Parsing is required to prevent Gson from escaping all the characters of the json

+      JsonElement testExecutionParsed = new JsonParser().parse(testExecution.toString());

+      res.add("testExecution", testExecutionParsed);

+      // Get the state of the process instance using the Camunda REST API

+      JsonObject procInstStatus = Utilities.Camunda.processInstanceStatus(executionId);

+      // Extract the state of the process instance from the JSON response

+      String processInstanceState =

+          procInstStatus.getAsJsonObject("historicProcessInstance").get("state").getAsString();

+      // Add the result to the final response

+      res.addProperty("state", processInstanceState);

+    } catch (NullPointerException npe) {

+      // In the case of a null pointer exception, make it clear that the state was not able

+      // to be determined using the Camunda API.

+      logger.error("Unable to determine the live status of the test execution.");

+      res.addProperty("state", "Unable to determine");

+    }

+    // Send the response

+    return Response.ok(res.toString()).build();

+  }

+

+  @Override

+  public Response getExecution(String authorization, String processInstanceId) {

+    try {

+      UUID.fromString(processInstanceId);

+    } catch (IllegalArgumentException e) {

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          "Invalid execution identifier. Expected type is UUID (v4).");

+    }

+

+    // try to find the test execution

+    Optional<TestExecution> optionalTestExecution =

+        testExecutionRepository.findFirstByProcessInstanceId(processInstanceId);

+    TestExecution testExecution = Utilities.resolveOptional(optionalTestExecution);

+    if (testExecution == null) {

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          String.format("An execution with identifier %s was not found.", processInstanceId));

+    }

+

+    // try to find the group of the test execution

+    String testExecutionGroupId = testExecution.getGroupId().toString();

+    Optional<Group> optionalGroup = groupRepository.findById(testExecutionGroupId);

+    Group group = Utilities.resolveOptional(optionalGroup);

+    if (group == null) {

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          String.format(

+              "The group (id: %s) associated with the test execution does not exist.",

+              testExecutionGroupId));

+    }

+

+    // try to find the user for the mechanizedId used to make this request

+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);

+    if (user == null) {

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          "No user associated with mechanized identifier used" + " for this request.");

+    }

+

+    // if it doesnt have read permission then return bad request

+    if (!PermissionChecker.hasPermissionTo(user,group,UserPermission.Permission.READ,groupRepository)){

+      return Utilities.Http.BuildResponse.unauthorizedWithMessage(

+          "Unauthorized to view this test execution.");

+    }

+

+    return Response.ok(testExecution.toString()).build();

+  }

+}

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
index 0000000..d171206
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestInstanceServiceImpl.java
@@ -0,0 +1,807 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service.impl;

+

+import org.oran.otf.api.Utilities;

+import org.oran.otf.api.Utilities.LogLevel;

+import org.oran.otf.api.handler.CamundaProcessExecutionHandler;

+import org.oran.otf.api.service.TestInstanceService;

+import org.oran.otf.common.model.Group;

+import org.oran.otf.common.model.TestDefinition;

+import org.oran.otf.common.model.TestInstance;

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.model.local.TestInstanceCreateRequest;

+import org.oran.otf.common.model.local.WorkflowRequest;

+import org.oran.otf.common.repository.GroupRepository;

+import org.oran.otf.common.repository.TestDefinitionRepository;

+import org.oran.otf.common.repository.TestInstanceRepository;

+import org.oran.otf.common.repository.UserRepository;

+import org.oran.otf.common.utility.Utility;

+import org.oran.otf.common.utility.database.Generic;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import org.oran.otf.common.utility.permissions.PermissionChecker;

+import org.oran.otf.common.utility.permissions.UserPermission;

+import com.google.common.base.Strings;

+import org.bson.types.ObjectId;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Service;

+

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+import java.util.*;

+

+@Service

+public class TestInstanceServiceImpl implements TestInstanceService {

+    @Autowired

+    CamundaProcessExecutionHandler camundaProcessExecutionHandler;

+    @Autowired

+    UserRepository userRepository;

+    @Autowired

+    TestInstanceRepository testInstanceRepository;

+    @Autowired

+    TestDefinitionRepository testDefinitionRepository;

+    @Autowired

+    GroupRepository groupRepository;

+

+    private static final Logger logger = LoggerFactory.getLogger(TestInstanceServiceImpl.class);

+    private static final String logPrefix = Utility.getLoggerPrefix();

+

+    @Override

+    public Response execute(String testInstanceId, String authorization, WorkflowRequest request) {

+        try {

+            if (request == null) {

+                return ResponseUtility.Build.badRequestWithMessage("Request body is null.");

+            }

+

+            // Check if the testInstanceId is a valid BSON ObjectI, otherwise return a bad request

+            // response.

+            if (!Utilities.isObjectIdValid(testInstanceId)) {

+                String error =

+                        String.format(

+                                "%sThe testInstanceId, %s, is not a valid ObjectId (BSON).",

+                                logPrefix, testInstanceId);

+                return ResponseUtility.Build.badRequestWithMessage(error);

+            }

+

+            // Create an ObjectId now that we know the provided String was valid.

+            ObjectId oiTestInstanceId = new ObjectId(testInstanceId);

+            // Check if the testInstance exists, otherwise return a not found response.

+            TestInstance testInstance = Generic.findByIdGeneric(testInstanceRepository, oiTestInstanceId);

+            if (testInstance == null) {

+                String error =

+                        String.format(

+                                "%sThe testInstance with _id, %s, was not found.", logPrefix, testInstanceId);

+                return ResponseUtility.Build.notFoundWithMessage(error);

+            }

+            // Check if the testDefinition exists.

+            TestDefinition testDefinition =

+                    Generic.findByIdGeneric(testDefinitionRepository, testInstance.getTestDefinitionId());

+            if (testDefinition == null) {

+                String error =

+                        String.format(

+                                "%sThe testDefinition with _id, %s, was not found.",

+                                logPrefix, testInstance.getTestDefinitionId().toString());

+                return ResponseUtility.Build.notFoundWithMessage(error);

+            }

+

+            // Check if a user associated with the mechanizedId used in the authorization header exists in

+            // the database.

+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);

+            if (mechanizedIdUser == null) {

+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);

+                if (decodedAuth == null) {

+                    return ResponseUtility.Build.badRequestWithMessage(

+                            String.format("Unable to decode authorization header: %s", authorization));

+                }

+                String error =

+                        String.format(

+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);

+                return ResponseUtility.Build.unauthorizedWithMessage(error);

+            }

+

+            // If the mechanizedId is not an OTF mechanizedId, check if the user is authorized to

+            // execute

+            // the test instance. This is required because the executorId only needs to be read from the

+            // otf-frontend component. The user/group system is not fully integrated with AAF, so this

+            // is

+            // required. A better way might be to use certificates to check identities.

+            Group testInstanceGroup = Utilities.resolveOptional(groupRepository.findById(testInstance.getGroupId().toString()));

+            // if we cant find the test instance group then we cant check the permission

+            if (testInstanceGroup == null) {

+                return ResponseUtility.Build.

+                        badRequestWithMessage(

+                                String.format("Can not find test instance group, id:%s", testInstance.getGroupId().toString()));

+            }

+            // If the mechanizedId is authorized, set the executorId in the WorkflowRequest to the

+            // mechanizedId's ObjectId to make sure that the executorId isn't spoofed. Only use the

+            // executorId sent with the request if it uses the OTF mechanizedId because we "trust" it.

+            if (isOtfMechanizedIdentifier(mechanizedIdUser.getEmail()) && request.getExecutorId() != null) {

+                mechanizedIdUser = Utilities.resolveOptional(userRepository.findById(request.getExecutorId().toString()));

+            } else {

+                request.setExecutorId(mechanizedIdUser.get_id());

+            }

+            if (!PermissionChecker.hasPermissionTo(mechanizedIdUser,testInstanceGroup, UserPermission.Permission.EXECUTE,groupRepository)) {

+                String error =

+                        String.format(

+                                "%sUnauthorized the execute test instance with _id, %s.",

+                                logPrefix, testInstanceId);

+                return ResponseUtility.Build.unauthorizedWithMessage(error);

+            }

+

+            // Set the test instance _id after authorization.

+            request.setTestInstanceId(testInstance.get_id());

+

+            // Check if the test instance is disabled.

+            if (testInstance.isDisabled()) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format("The test instance with identifier %s is disabled.", testInstanceId));

+            }

+            // Check if the test definition is disabled.

+            if (testDefinition.isDisabled()) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format(

+                                "The test definition with identifier %s is disabled.",

+                                testInstance.getTestDefinitionId().toString()));

+            }

+

+            // Send the request to Camunda.

+            return camundaProcessExecutionHandler.startProcessInstance(request);

+        } catch (Exception e) {

+            Utilities.printStackTrace(e, LogLevel.ERROR);

+            return ResponseUtility.Build.internalServerError();

+        }

+    }

+

+    @Override

+    public Response createByTestDefinitionId(

+            String testDefinitionId, String authorization, TestInstanceCreateRequest request) {

+        try {

+            // Check if a user associated with the mechanizedId used in the authorization header exists in

+            // the database.

+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);

+            if (mechanizedIdUser == null) {

+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);

+                if (decodedAuth == null) {

+                    return ResponseUtility.Build.badRequestWithMessage(

+                            String.format("Unable to decode authorization header: %s", authorization));

+                }

+                String error =

+                        String.format(

+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);

+                return ResponseUtility.Build.unauthorizedWithMessage(error);

+            }

+

+            // Check if the String correctly parses as an ObjectId.

+            if (!Utilities.isObjectIdValid(testDefinitionId)) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format(

+                                "The testDefinitionId %s is not a valid BSON ObjectId.", testDefinitionId));

+            }

+            ObjectId oiTestDefintionId = new ObjectId(testDefinitionId);

+

+            // Find the testDefinition

+            TestDefinition testDefinition =

+                    Generic.findByIdGeneric(testDefinitionRepository, oiTestDefintionId);

+            if (testDefinition == null) {

+                return ResponseUtility.Build.notFoundWithMessage(

+                        String.format("Test definition with id, %s, was not found.", testDefinitionId));

+            }

+            // Check if the mechanizedId has access to the test definition.

+            Group testDefGroup = Utilities.resolveOptional(groupRepository.findById(testDefinition.getGroupId().toString()));

+            if (testDefGroup == null) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format("Can not find test definition's group, id: %s", testDefinition.getGroupId().toString()));

+            }

+//            if (PermissionChecker.hasReadPermission(mechanizedIdUser, testDefGroup, groupRepository)) {

+//                return ResponseUtility.Build.unauthorizedWithMessage(

+//                        String.format(

+//                                "MechanizedId, %s, does not have read access to test definition in group with name, %s",

+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+//            }

+//            if (PermissionChecker.hasWritePermission(mechanizedIdUser, testDefGroup)) {

+//                return ResponseUtility.Build.unauthorizedWithMessage(

+//                        String.format(

+//                                "MechanizedId, %s, does not have write access to the group with name, %s",

+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+//            }

+            if (PermissionChecker.hasPermissionTo(mechanizedIdUser, testDefGroup,

+                    Arrays.asList(UserPermission.Permission.READ,UserPermission.Permission.WRITE),groupRepository))

+            {

+                return ResponseUtility.Build.unauthorizedWithMessage(

+                        String.format(

+                                "MechanizedId, %s, does not have access (read/write) to the group with name, %s",

+                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+            }

+            // Get the latest version of the test definition to link it with the test instance

+            BpmnInstance bpmnInstance = findBpmnInstance(testDefinition, Integer.MIN_VALUE, true);

+            if (bpmnInstance == null) {

+                return ResponseUtility.Build.notFoundWithMessage(

+                        String.format(

+                                "Test definition with id, %s, does not have any versions associated with it.",

+                                testDefinitionId));

+            }

+

+            TestInstance testInstance =

+                    new TestInstance(

+                            new ObjectId(),

+                            request.getTestInstanceName(),

+                            request.getTestInstanceDescription(),

+                            testDefinition.getGroupId(),

+                            testDefinition.get_id(),

+                            bpmnInstance.getProcessDefinitionId(),

+                            request.isUseLatestTestDefinition(),

+                            false,

+                            request.isSimulationMode(),

+                            request.getMaxExecutionTimeInMillis(),

+                            request.getPfloInput(),

+                            new HashMap<>(),

+                            request.getSimulationVthInput(),

+                            request.getTestData(),

+                            request.getVthInput(),

+                            new Date(System.currentTimeMillis()),

+                            new Date(System.currentTimeMillis()),

+                            mechanizedIdUser.get_id(),

+                            mechanizedIdUser.get_id());

+

+            return Response.ok()

+                    .type(MediaType.APPLICATION_JSON_TYPE)

+                    .entity(testInstance.toString())

+                    .build();

+        } catch (Exception e) {

+            Utilities.printStackTrace(e, LogLevel.ERROR);

+            return ResponseUtility.Build.internalServerError();

+        }

+    }

+

+    @Override

+    public Response createByTestDefinitionId(

+            String testDefinitionId,

+            int version,

+            String authorization,

+            TestInstanceCreateRequest request) {

+        try {

+            // Check if a user associated with the mechanizedId used in the authorization header exists in

+            // the database.

+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);

+            if (mechanizedIdUser == null) {

+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);

+                if (decodedAuth == null) {

+                    return ResponseUtility.Build.badRequestWithMessage(

+                            String.format("Unable to decode authorization header: %s", authorization));

+                }

+                String error =

+                        String.format(

+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);

+                return ResponseUtility.Build.unauthorizedWithMessage(error);

+            }

+

+            // Check if the String correctly parses as an ObjectId.

+            if (!Utilities.isObjectIdValid(testDefinitionId)) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format(

+                                "The testDefinitionId %s is not a valid BSON ObjectId.", testDefinitionId));

+            }

+            ObjectId oiTestDefintionId = new ObjectId(testDefinitionId);

+

+            // Find the testDefinition

+            TestDefinition testDefinition =

+                    Generic.findByIdGeneric(testDefinitionRepository, oiTestDefintionId);

+            if (testDefinition == null) {

+                return ResponseUtility.Build.notFoundWithMessage(

+                        String.format("Test definition with id, %s, was not found.", testDefinitionId));

+            }

+            // permission checking

+            Group testDefGroup = Utilities.resolveOptional(groupRepository.findById(testDefinition.getGroupId().toString()));

+            if (testDefGroup == null) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format("Can not find test definition's group, id: %s", testDefinition.getGroupId().toString()));

+            }

+            // if not otf email and is not authorized

+//            if (PermissionChecker.hasReadPermission(mechanizedIdUser, testDefGroup, groupRepository)) {

+////                return ResponseUtility.Build.unauthorizedWithMessage(

+////                        String.format(

+////                                "MechanizedId, %s, does not have read access to test definition in group with name, %s",

+////                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+////            }

+////            if (PermissionChecker.hasWritePermission(mechanizedIdUser, testDefGroup)) {

+////                return ResponseUtility.Build.unauthorizedWithMessage(

+////                        String.format(

+////                                "MechanizedId, %s, does not have write access to the group with name, %s",

+////                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+////            }

+            if (PermissionChecker.hasPermissionTo(mechanizedIdUser, testDefGroup,

+                    Arrays.asList(UserPermission.Permission.READ,UserPermission.Permission.WRITE),groupRepository))

+            {

+                return ResponseUtility.Build.unauthorizedWithMessage(

+                        String.format(

+                                "MechanizedId, %s, does not have access (read/write) to the group with name, %s",

+                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+            }

+            // Get the latest version of the test definition to link it with the test instance

+            BpmnInstance bpmnInstance = findBpmnInstance(testDefinition, version, false);

+            if (bpmnInstance == null) {

+                return ResponseUtility.Build.notFoundWithMessage(

+                        String.format(

+                                "Test definition with id, %s, does not have any versions associated with it.",

+                                testDefinitionId));

+            }

+

+            TestInstance testInstance =

+                    new TestInstance(

+                            new ObjectId(),

+                            request.getTestInstanceName(),

+                            request.getTestInstanceDescription(),

+                            testDefinition.getGroupId(),

+                            testDefinition.get_id(),

+                            bpmnInstance.getProcessDefinitionId(),

+                            request.isUseLatestTestDefinition(),

+                            false,

+                            request.isSimulationMode(),

+                            request.getMaxExecutionTimeInMillis(),

+                            request.getPfloInput(),

+                            new HashMap<>(),

+                            request.getSimulationVthInput(),

+                            request.getTestData(),

+                            request.getVthInput(),

+                            new Date(System.currentTimeMillis()),

+                            new Date(System.currentTimeMillis()),

+                            mechanizedIdUser.get_id(),

+                            mechanizedIdUser.get_id());

+

+            return Response.ok()

+                    .type(MediaType.APPLICATION_JSON_TYPE)

+                    .entity(testInstance.toString())

+                    .build();

+        } catch (Exception e) {

+            Utilities.printStackTrace(e, LogLevel.ERROR);

+            return ResponseUtility.Build.internalServerError();

+        }

+    }

+

+    @Override

+    public Response createByProcessDefinitionKey(

+            String processDefinitionKey, String authorization, TestInstanceCreateRequest request) {

+        try {

+            // Check if a user associated with the mechanizedId used in the authorization header exists in

+            // the database.

+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);

+            if (mechanizedIdUser == null) {

+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);

+                if (decodedAuth == null) {

+                    return ResponseUtility.Build.badRequestWithMessage(

+                            String.format("Unable to decode authorization header: %s", authorization));

+                }

+                String error =

+                        String.format(

+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);

+                return ResponseUtility.Build.unauthorizedWithMessage(error);

+            }

+

+            // Check if the String correctly parses as an ObjectId.

+            if (Strings.isNullOrEmpty(processDefinitionKey)) {

+                return ResponseUtility.Build.badRequestWithMessage("The processDefinitionKey is required.");

+            }

+

+            // Find the testDefinition

+            TestDefinition testDefinition =

+                    testDefinitionRepository.findByProcessDefinitionKey(processDefinitionKey).orElse(null);

+            if (testDefinition == null) {

+                return ResponseUtility.Build.notFoundWithMessage(

+                        String.format(

+                                "Test definition with processDefinitionKey, %s, was not found.",

+                                processDefinitionKey));

+            }

+

+            Group testDefGroup = Utilities.resolveOptional(groupRepository.findById(testDefinition.getGroupId().toString()));

+            if (testDefGroup == null) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format("Can not find test definition's group, id: %s", testDefinition.getGroupId().toString()));

+            }

+            // if not otf email and is not authorized

+//            if (PermissionChecker.hasReadPermission(mechanizedIdUser, testDefGroup, groupRepository)) {

+//                return ResponseUtility.Build.unauthorizedWithMessage(

+//                        String.format(

+//                                "MechanizedId, %s, does not have read access to test definition in group with name, %s",

+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+//            }

+//            if (PermissionChecker.hasWritePermission(mechanizedIdUser, testDefGroup)) {

+//                return ResponseUtility.Build.unauthorizedWithMessage(

+//                        String.format(

+//                                "MechanizedId, %s, does not have write access to the group with name, %s",

+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+//            }

+            if (PermissionChecker.hasPermissionTo(mechanizedIdUser, testDefGroup,

+                    Arrays.asList(UserPermission.Permission.READ,UserPermission.Permission.WRITE),groupRepository))

+            {

+                return ResponseUtility.Build.unauthorizedWithMessage(

+                        String.format(

+                                "MechanizedId, %s, does not have access (read/write) to the group with name, %s",

+                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+            }

+            // Get the latest version of the test definition to link it with the test instance

+            BpmnInstance bpmnInstance = findBpmnInstance(testDefinition, Integer.MIN_VALUE, false);

+            if (bpmnInstance == null) {

+                return ResponseUtility.Build.notFoundWithMessage(

+                        String.format(

+                                "Test definition with id, %s, does not have any versions associated with it.",

+                                testDefinition.get_id().toString()));

+            }

+

+            TestInstance testInstance =

+                    new TestInstance(

+                            new ObjectId(),

+                            request.getTestInstanceName(),

+                            request.getTestInstanceDescription(),

+                            testDefinition.getGroupId(),

+                            testDefinition.get_id(),

+                            bpmnInstance.getProcessDefinitionId(),

+                            request.isUseLatestTestDefinition(),

+                            false,

+                            request.isSimulationMode(),

+                            request.getMaxExecutionTimeInMillis(),

+                            request.getPfloInput(),

+                            new HashMap<>(),

+                            request.getSimulationVthInput(),

+                            request.getTestData(),

+                            request.getVthInput(),

+                            new Date(System.currentTimeMillis()),

+                            new Date(System.currentTimeMillis()),

+                            mechanizedIdUser.get_id(),

+                            mechanizedIdUser.get_id());

+

+            return Response.ok()

+                    .type(MediaType.APPLICATION_JSON_TYPE)

+                    .entity(testInstance.toString())

+                    .build();

+        } catch (Exception e) {

+            Utilities.printStackTrace(e, LogLevel.ERROR);

+            return ResponseUtility.Build.internalServerError();

+        }

+    }

+

+    @Override

+    public Response createByProcessDefinitionKey(

+            String processDefinitionKey,

+            int version,

+            String authorization,

+            TestInstanceCreateRequest request) {

+        try {

+            // Check if a user associated with the mechanizedId used in the authorization header exists in

+            // the database.

+            User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);

+            if (mechanizedIdUser == null) {

+                String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);

+                if (decodedAuth == null) {

+                    return ResponseUtility.Build.badRequestWithMessage(

+                            String.format("Unable to decode authorization header: %s", authorization));

+                }

+                String error =

+                        String.format(

+                                "%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);

+                return ResponseUtility.Build.unauthorizedWithMessage(error);

+            }

+

+            // Check if the String correctly parses as an ObjectId.

+            if (Strings.isNullOrEmpty(processDefinitionKey)) {

+                return ResponseUtility.Build.badRequestWithMessage("The processDefinitionKey is required.");

+            }

+

+            // Find the testDefinition

+            TestDefinition testDefinition =

+                    testDefinitionRepository.findByProcessDefinitionKey(processDefinitionKey).orElse(null);

+            if (testDefinition == null) {

+                return ResponseUtility.Build.notFoundWithMessage(

+                        String.format(

+                                "Test definition with processDefinitionKey, %s, was not found.",

+                                processDefinitionKey));

+            }

+

+            Group testDefGroup = Utilities.resolveOptional(groupRepository.findById(testDefinition.getGroupId().toString()));

+            if (testDefGroup == null) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format("Can not find test definition's group, id: %s", testDefinition.getGroupId().toString()));

+            }

+            // if not otf email and is not authorized

+//            if (PermissionChecker.hasReadPermission(mechanizedIdUser, testDefGroup, groupRepository)) {

+//                return ResponseUtility.Build.unauthorizedWithMessage(

+//                        String.format(

+//                                "MechanizedId, %s, does not have read access to test definition in group with name, %s",

+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+//            }

+//            if (PermissionChecker.hasWritePermission(mechanizedIdUser, testDefGroup)) {

+//                return ResponseUtility.Build.unauthorizedWithMessage(

+//                        String.format(

+//                                "MechanizedId, %s, does not have write access to the group with name, %s",

+//                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+//            }

+            if (PermissionChecker.hasPermissionTo(mechanizedIdUser, testDefGroup,

+                    Arrays.asList(UserPermission.Permission.READ,UserPermission.Permission.WRITE),groupRepository))

+            {

+                return ResponseUtility.Build.unauthorizedWithMessage(

+                        String.format(

+                                "MechanizedId, %s, does not have access (read/write) to the group with name, %s",

+                                mechanizedIdUser.getEmail(), testDefGroup.getGroupName()));

+            }

+            // Get the latest version of the test definition to link it with the test instance

+            BpmnInstance bpmnInstance = findBpmnInstance(testDefinition, version, false);

+            if (bpmnInstance == null) {

+                return ResponseUtility.Build.notFoundWithMessage(

+                        String.format(

+                                "Test definition with id, %s, does not have any versions associated with it.",

+                                testDefinition.get_id().toString()));

+            }

+

+            TestInstance testInstance =

+                    new TestInstance(

+                            new ObjectId(),

+                            request.getTestInstanceName(),

+                            request.getTestInstanceDescription(),

+                            testDefinition.getGroupId(),

+                            testDefinition.get_id(),

+                            bpmnInstance.getProcessDefinitionId(),

+                            request.isUseLatestTestDefinition(),

+                            false,

+                            request.isSimulationMode(),

+                            request.getMaxExecutionTimeInMillis(),

+                            request.getPfloInput(),

+                            new HashMap<>(),

+                            request.getSimulationVthInput(),

+                            request.getTestData(),

+                            request.getVthInput(),

+                            new Date(System.currentTimeMillis()),

+                            new Date(System.currentTimeMillis()),

+                            mechanizedIdUser.get_id(),

+                            mechanizedIdUser.get_id());

+

+            return Response.ok()

+                    .type(MediaType.APPLICATION_JSON_TYPE)

+                    .entity(testInstance.toString())

+                    .build();

+        } catch (Exception e) {

+            Utilities.printStackTrace(e, LogLevel.ERROR);

+            return ResponseUtility.Build.internalServerError();

+        }

+    }

+

+    @Override

+    public Response findById(String testInstanceId, String authorization) {

+        // Check if a user associated with the mechanizedId used in the authorization header exists in

+        // the database.

+        User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);

+        if (mechanizedIdUser == null) {

+            String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);

+            if (decodedAuth == null) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format("Unable to decode authorization header: %s", authorization));

+            }

+            String error =

+                    String.format("%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);

+            return ResponseUtility.Build.unauthorizedWithMessage(error);

+        }

+

+        // Check if the testInstanceId is a valid BSON ObjectI, otherwise return a bad request

+        // response.

+        if (!Utilities.isObjectIdValid(testInstanceId)) {

+            String error =

+                    String.format(

+                            "%sThe testInstanceId, %s, is not a valid ObjectId (BSON).",

+                            logPrefix, testInstanceId);

+            return ResponseUtility.Build.badRequestWithMessage(error);

+        }

+

+        // Create an ObjectId now that we know the provided String was valid.

+        ObjectId oiTestInstanceId = new ObjectId(testInstanceId);

+        // Check if the testInstance exists, otherwise return a not found response.

+        TestInstance testInstance = Generic.findByIdGeneric(testInstanceRepository, oiTestInstanceId);

+        if (testInstance == null) {

+            String error =

+                    String.format(

+                            "%sThe testInstance with _id, %s, was not found.", logPrefix, testInstanceId);

+            return ResponseUtility.Build.notFoundWithMessage(error);

+        }

+

+        Group testInstanceGroup = Utilities.resolveOptional(groupRepository.findById(testInstance.getGroupId().toString()));

+        if (testInstanceGroup == null) {

+            return ResponseUtility.Build.badRequestWithMessage(

+                    String.format("Can not find test instance's group, group name :%s", testInstance.get_id().toString()));

+        }

+        if (!PermissionChecker.hasPermissionTo(mechanizedIdUser,testInstanceGroup,UserPermission.Permission.READ,groupRepository)) {

+            return ResponseUtility.Build.unauthorizedWithMessage(

+                    String.format(

+                            "User %s does not have read access to test instance group, group name: %s.",

+                            mechanizedIdUser.getEmail(), testInstanceGroup.getGroupName()));

+        }

+        return Response.ok(testInstance.toString(), MediaType.APPLICATION_JSON_TYPE).build();

+    }

+

+    @Override

+    public Response findByProcessDefinitionKey(String processDefinitionKey, String authorization) {

+        // Check if a user associated with the mechanizedId used in the authorization header exists in

+        // the database.

+        User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);

+        if (mechanizedIdUser == null) {

+            String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);

+            if (decodedAuth == null) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format("Unable to decode authorization header: %s", authorization));

+            }

+            String error =

+                    String.format("%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);

+            return ResponseUtility.Build.unauthorizedWithMessage(error);

+        }

+

+        Optional<TestDefinition> optionalTestDefinition =

+                testDefinitionRepository.findByProcessDefinitionKey(processDefinitionKey);

+        TestDefinition testDefinition = optionalTestDefinition.orElse(null);

+        if (testDefinition == null) {

+            return Utilities.Http.BuildResponse.badRequestWithMessage(

+                    String.format(

+                            "Cannot find test instance because a test"

+                                    + " definition with the process definition key (%s) does not exist.",

+                            processDefinitionKey));

+        }

+

+        List<TestInstance> testInstances =

+                testInstanceRepository.findAllByTestDefinitionId(testDefinition.get_id());

+        if (testInstances.isEmpty()) {

+            return Utilities.Http.BuildResponse.badRequestWithMessage(

+                    String.format(

+                            "No test instances found with process " + "definition key (%s).",

+                            processDefinitionKey));

+        }

+

+        List<TestInstance> result = new ArrayList<>();

+        for (TestInstance testInstance : testInstances) {

+            Group testInstanceGroup = Utilities.resolveOptional(groupRepository.findById(testInstance.getGroupId().toString()));

+            if (testInstanceGroup != null && PermissionChecker.hasPermissionTo(mechanizedIdUser,testInstanceGroup,UserPermission.Permission.READ,groupRepository)) {

+                result.add(testInstance);

+            }

+        }

+

+        return Response.ok(result.toString()).build();

+    }

+

+    @Override

+    public Response findByProcessDefinitionKeyAndVersion(

+            String processDefinitionKey, String version, String authorization) {

+        // Check if a user associated with the mechanizedId used in the authorization header exists in

+        // the database.

+        User mechanizedIdUser = Utilities.findUserByAuthHeader(authorization, userRepository);

+        if (mechanizedIdUser == null) {

+            String[] decodedAuth = Utilities.decodeBase64AuthorizationHeader(authorization);

+            if (decodedAuth == null) {

+                return ResponseUtility.Build.badRequestWithMessage(

+                        String.format("Unable to decode authorization header: %s", authorization));

+            }

+            String error =

+                    String.format("%sMechanizedId is not onboarded with OTF. %s.", logPrefix, decodedAuth[0]);

+            return ResponseUtility.Build.unauthorizedWithMessage(error);

+        }

+

+        Optional<TestDefinition> optionalTestDefinition =

+                testDefinitionRepository.findByProcessDefinitionKey(processDefinitionKey);

+        TestDefinition testDefinition = optionalTestDefinition.orElse(null);

+

+        if (testDefinition == null) {

+            return Utilities.Http.BuildResponse.badRequestWithMessage(

+                    String.format(

+                            "Cannot find test instance because a test"

+                                    + " definition with the process definition key (%s) does not exist.",

+                            processDefinitionKey));

+        }

+

+        int iVersion;

+        try {

+            iVersion = Integer.parseInt(version);

+        } catch (NumberFormatException nfe) {

+            return Utilities.Http.BuildResponse.badRequestWithMessage("Version must be a valid integer.");

+        }

+

+        BpmnInstance bpmnInstance =

+                testDefinition.getBpmnInstances().stream()

+                        .filter(_bpmnInstance -> _bpmnInstance.getVersion() == iVersion)

+                        .findAny()

+                        .orElse(null);

+

+        if (bpmnInstance == null) {

+            return Utilities.Http.BuildResponse.badRequestWithMessage(

+                    String.format("Cannot find any test instances using " + "version %s.", version));

+        }

+

+        List<TestInstance> testInstances =

+                testInstanceRepository.findAllByTestDefinitionIdAndPDId(

+                        testDefinition.get_id(), bpmnInstance.getProcessDefinitionId());

+

+        if (testInstances.isEmpty()) {

+            return Utilities.Http.BuildResponse.badRequestWithMessage(

+                    String.format(

+                            "No test instances found with process " + "definition key (%s).",

+                            processDefinitionKey));

+        }

+

+        List<TestInstance> result = new ArrayList<>();

+        for (TestInstance testInstance : testInstances) {

+            Group testInstanceGroup = Utilities.resolveOptional(groupRepository.findById(testInstance.getGroupId().toString()));

+            if (testInstanceGroup != null && PermissionChecker.hasPermissionTo(mechanizedIdUser,testInstanceGroup,UserPermission.Permission.READ,groupRepository)) {

+                result.add(testInstance);

+            }

+        }

+

+        return Response.ok(result.toString()).build();

+    }

+

+    private boolean isOtfMechanizedIdentifier(String email) {

+        return email.equalsIgnoreCase("email@localhost")

+                || email.equalsIgnoreCase("email@localhost")

+                || email.equalsIgnoreCase("email@localhost")

+                || email.equalsIgnoreCase("email@localhost")

+                || email.equalsIgnoreCase("email@localhost");

+    }

+

+    private BpmnInstance findBpmnInstance(TestDefinition testDefinition, int version, boolean latest)

+            throws Exception {

+        BpmnInstance bpmnInstance = null;

+        int maxVersion = Integer.MIN_VALUE;

+        // Check if the version exists

+        for (BpmnInstance bi : testDefinition.getBpmnInstances()) {

+            // If this field is null or empty, it means the bpmn hasn't been deployed, or there was a

+            // creation error on the Test Definition page (UI). Skip the field so the user isn't allowed

+            // to create a test instance based off this bpmn instance.

+            if (Strings.isNullOrEmpty(bi.getProcessDefinitionId())) {

+                continue;

+            }

+

+            // Split the processDefinitionId based on it's format:

+            // {processDefinitionKey}:{version}:{processDefinitionId}.

+            String processDefinitionId = bi.getProcessDefinitionId();

+            String[] processDefinitionIdSplit = processDefinitionId.split(":");

+            if (processDefinitionIdSplit.length != 3) {

+                throw new Exception(

+                        String.format(

+                                "testDefinition[%s].bpmnInstances.processDefinitionId[%s] is invalid.",

+                                testDefinition.get_id().toString(), bi.getProcessDefinitionId()));

+            }

+

+            String sVersion = processDefinitionIdSplit[1];

+            int currentVersion = Integer.parseInt(sVersion);

+            if (latest && currentVersion > maxVersion) {

+                bpmnInstance = bi;

+            } else if (currentVersion == version) {

+                bpmnInstance = bi;

+                break;

+            }

+        }

+

+        return bpmnInstance;

+    }

+

+//    private boolean isAuthorized(User user, Group group, String permission, GroupRepository groupRepository) {

+//        if (isOtfMechanizedIdentifier(user.getEmail())) {

+//            return true;

+//        }

+//        return PermissionChecker.isAuthorized(user, group, permission.toUpperCase(), groupRepository);

+//    }

+}

+/*

+ PermissionChecker.hasReadPermission(mechanizedIdUser,testInstanceGroup,groupRepository)

+

+  PermissionChecker.hasPermission(mechanizedIdUser,testInstanceGroup,groupRepository, [READ, WRITE])

+  PermissionsMAp = PermissionChecker.Build.hasRead

+ */
\ 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
index 0000000..13d125a
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/TestStrategyServiceImpl.java
@@ -0,0 +1,228 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service.impl;

+

+import org.oran.otf.api.Utilities;

+import org.oran.otf.api.handler.CamundaProcessDeploymentHandler;

+import org.oran.otf.api.service.TestStrategyService;

+import org.oran.otf.common.model.TestDefinition;

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.model.local.DeployTestStrategyRequest;

+import org.oran.otf.common.model.local.OTFApiResponse;

+import org.oran.otf.common.repository.GroupRepository;

+import org.oran.otf.common.repository.TestDefinitionRepository;

+import org.oran.otf.common.repository.UserRepository;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import com.fasterxml.jackson.databind.ObjectMapper;

+import io.swagger.v3.oas.annotations.Hidden;

+import java.io.IOException;

+import java.io.InputStream;

+import java.util.Base64;

+import java.util.Optional;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+import org.apache.http.HttpResponse;

+import org.apache.http.conn.HttpHostConnectException;

+import org.apache.http.util.EntityUtils;

+import org.bson.types.ObjectId;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.stereotype.Service;

+

+@Service

+@Hidden

+public class TestStrategyServiceImpl implements TestStrategyService {

+

+  private static final Logger logger = LoggerFactory.getLogger(TestStrategyServiceImpl.class);

+

+  @Autowired private TestDefinitionRepository testDefinitionRepository;

+

+  @Autowired private UserRepository userRepository;

+

+  @Autowired private CamundaProcessDeploymentHandler camundaProcessDeploymentHandler;

+

+  @Autowired private GroupRepository groupRepository;

+

+  public Response deployTestStrategy(

+      InputStream bpmn,

+      InputStream compressedResources,

+      String testDefinitionId,

+      String testDefinitionDeployerId,

+      String definitionId,

+      String authorization) {

+    if (bpmn == null)

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          "BPMN input stream cannot be null.");

+

+    // Decode the authorization header.

+    byte[] decodedAuthorization = Base64.getDecoder().decode(authorization.replace("Basic ", ""));

+    String credentials = new String(decodedAuthorization);

+    String[] credentialsArray = credentials.split(":");

+

+    /* Check if the request came from the system specified mechanized identifier. The request goes through AAF

+     * authorization before reaching this code, therefore, assume the headers aren't spoofed. */

+    if (!credentialsArray[0].equals(System.getenv("AAF_ID")))

+      return Utilities.Http.BuildResponse.badRequestWithMessage(

+          "Unauthorized to use this service.");

+

+    // Map to a POJO model2.

+    ObjectId _testDefinitionDeployerId = null;

+    ObjectId _testDefinitionId = null;

+

+    if (testDefinitionDeployerId != null && ObjectId.isValid(testDefinitionDeployerId))

+      _testDefinitionDeployerId = new ObjectId(testDefinitionDeployerId);

+    if (testDefinitionId != null && ObjectId.isValid(testDefinitionId))

+      _testDefinitionId = new ObjectId(testDefinitionId);

+

+    DeployTestStrategyRequest request =

+        new DeployTestStrategyRequest(_testDefinitionDeployerId, _testDefinitionId, definitionId);

+

+    //		String bpmnContents = null;

+    //		try (final Reader reader = new InputStreamReader(bpmn)) {

+    //			bpmnContents = CharStreams.toString(reader);

+    //	 		} catch (Exception e) {

+    //			e.printStackTrace();

+    //		}

+

+    // Check if the request actually contains a bpmn string.

+    //		try {

+    //			if (bpmnContents == null || bpmnContents.trim().length() == 0)

+    //				return Utilities.Http.BuildResponse.badRequestWithMessage("BPMN contents are null.");

+    //		} catch (Exception e) {

+    //			logger.error(Utilities.getStackTrace(e));

+    //		}

+

+    // If a test definition id is supplied, the request intends to update an existing test

+    // definition.

+    if (request.getTestDefinitionId() != null) {

+      // Check if the test definition exists in the database.

+      Optional<TestDefinition> testDefinitionOptional =

+          testDefinitionRepository.findById(request.getTestDefinitionId().toString());

+

+      if (!testDefinitionOptional.isPresent())

+        return Utilities.Http.BuildResponse.badRequestWithMessage(

+            String.format("Test definition (%s) was not found.", request.getTestDefinitionId()));

+

+      // Check if a user to update the definition was supplied.

+      if (request.getTestDefinitionDeployerId() == null)

+        return Utilities.Http.BuildResponse.badRequestWithMessage(

+            "Must specify testDefinitionDeployerId.");

+

+      // Check if the user requesting to update the definition is the user who originally created

+      // the definition.

+      TestDefinition testDefinition = testDefinitionOptional.get();

+

+      if (!testDefinition

+          .getCreatedBy()

+          .toString()

+          .equals(request.getTestDefinitionDeployerId().toString()))

+        return Utilities.Http.BuildResponse.badRequestWithMessage(

+            String.format(

+                "User (%s) is not authorized to update this test definition.",

+                request.getTestDefinitionDeployerId()));

+

+      // Check if the version to deploy already exists

+      for (BpmnInstance bpmnInstance : testDefinition.getBpmnInstances()) {

+        if (bpmnInstance.getProcessDefinitionId().equalsIgnoreCase(request.getDefinitionId()))

+          return Utilities.Http.BuildResponse.badRequestWithMessage(

+              String.format(

+                  "A deployment with the definitionId %s already exists.",

+                  request.getDefinitionId()));

+      }

+    }

+

+    // Make the deployment request to Camunda. Relay the response received by Camunda.

+    return camundaProcessDeploymentHandler.start(bpmn, compressedResources);

+  }

+

+  public Response deleteByDeploymentId(String deploymentId, String authorization) {

+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);

+    if (!isAuthorized(authorization)) {

+      return Utilities.Http.BuildResponse.unauthorized();

+    }

+

+    String url =

+        String.format(

+            "%s:%s/%s/%s",

+            System.getenv("otf.camunda.host"),

+            System.getenv("otf.camunda.port"),

+            System.getenv("otf.camunda.deploymentDeletionUri"),

+            deploymentId);

+

+    try {

+      HttpResponse res = Utilities.Http.httpDeleteAAF(url);

+      String resStr = EntityUtils.toString(res.getEntity());

+      int status = res.getStatusLine().getStatusCode();

+      return Response.status(status)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(status, resStr))

+          .build();

+

+    } catch (Exception e) {

+      e.printStackTrace();

+      return Utilities.Http.BuildResponse.internalServerError();

+    }

+  }

+

+  public Response deleteByTestDefinitionId(String testDefinitionId, String authorization) {

+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);

+    if (!isAuthorized(authorization)) {

+      return Utilities.Http.BuildResponse.unauthorizedWithMessage("Authorization headers not set.");

+    }

+

+    String url =

+        String.format(

+            "%s:%s/%s/%s",

+            System.getenv("otf.camunda.host"),

+            System.getenv("otf.camunda.port"),

+            System.getenv("otf.camunda.testDefinitionDeletionUri"),

+            testDefinitionId);

+

+    try {

+      HttpResponse res = Utilities.Http.httpDeleteAAF(url);

+      String resStr = EntityUtils.toString(res.getEntity());

+      int status = res.getStatusLine().getStatusCode();

+      return Response.status(status)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(status, resStr))

+          .build();

+    } catch (HttpHostConnectException e) {

+      return ResponseUtility.Build.serviceUnavailableWithMessage(e.getMessage());

+    } catch (Exception e) {

+      e.printStackTrace();

+      return Utilities.Http.BuildResponse.internalServerError();

+    }

+  }

+

+  private boolean isAuthorized(String authorization) {

+    User user = Utilities.findUserByAuthHeader(authorization, userRepository);

+    return (user.getEmail().equalsIgnoreCase("email@localhost")

+        || user.getEmail().equalsIgnoreCase("email@localhost"));

+  }

+

+  private DeployTestStrategyRequest mapToDeployTestStrategyRequest(String body) {

+    ObjectMapper mapper = new ObjectMapper();

+    try {

+      return mapper.readValue(body, DeployTestStrategyRequest.class); // Perform the mapping

+    } catch (IOException e) { // Indicates an unknown request body

+      logger.error(e.getMessage());

+      return null;

+    }

+  }

+}

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
index 0000000..d2a662b
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/api/service/impl/VirtualTestHeadServiceImpl.java
@@ -0,0 +1,164 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.service.impl;

+

+import org.oran.otf.api.Utilities;

+import org.oran.otf.api.service.VirtualTestHeadService;

+import org.oran.otf.common.model.Group;

+import org.oran.otf.common.model.TestHead;

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.repository.GroupRepository;

+import org.oran.otf.common.repository.TestHeadRepository;

+import org.oran.otf.common.repository.UserRepository;

+import org.oran.otf.common.utility.Utility;

+import org.oran.otf.common.utility.database.Generic;

+import org.oran.otf.common.utility.http.ResponseUtility;

+import org.oran.otf.common.utility.permissions.PermissionChecker;

+import org.oran.otf.common.utility.permissions.UserPermission;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+import org.springframework.stereotype.Service;

+

+import javax.ws.rs.core.Response;

+import java.util.Date;

+import java.util.Optional;

+

+@Service

+public class VirtualTestHeadServiceImpl implements VirtualTestHeadService {

+

+    @Autowired

+    private UserRepository userRepository;

+    @Autowired

+    private GroupRepository groupRepository;

+

+    @Autowired

+    TestHeadRepository testHeadRepository;

+

+    @Autowired

+    MongoTemplate mongoTemplate;

+

+    private static final String logPrefix = Utility.getLoggerPrefix();

+

+    @Override

+    public Response updateVirtualTestHead(String authorization, String testHeadName, TestHead newTestHead) {

+        if (authorization == null) {

+            return Utilities.Http.BuildResponse.unauthorizedWithMessage("Missing authorization header.");

+        }

+

+        // try to find the test head

+        Optional<TestHead> optionalTestHead =

+                testHeadRepository.findByTestHeadName(testHeadName);

+        TestHead testHead = Utilities.resolveOptional(optionalTestHead);

+        if (testHead == null) {

+            return Utilities.Http.BuildResponse.badRequestWithMessage(

+                    String.format("A test head with identifier %s was not found.", testHeadName));

+        }

+

+        // try to find the group of the test head

+        String testHeadGroupId = testHead.getGroupId().toString();

+        Group testHeadGroup = Generic.findByIdGeneric(groupRepository, testHead.getGroupId());

+        if (testHeadGroup == null) {

+            return Utilities.Http.BuildResponse.badRequestWithMessage(

+                    String.format(

+                            "The group (id: %s) associated with the test head does not exist.",

+                            testHeadGroupId));

+        }

+

+        // try to find the user for the mechanizedId used to make this request

+        User user = Utilities.findUserByAuthHeader(authorization, userRepository);

+        if (user == null) {

+            return Utilities.Http.BuildResponse.badRequestWithMessage(

+                    "No user associated with mechanized identifier used for this request.");

+        }

+

+        if (!PermissionChecker.hasPermissionTo(user, testHeadGroup, UserPermission.Permission.WRITE, groupRepository)) {

+            String error =

+                    String.format(

+                            "Unauthorized the write to test head with name, %s.",

+                            testHeadGroupId);

+            return ResponseUtility.Build.unauthorizedWithMessage(error);

+        }

+

+        return updateTestHeadFields(testHead, newTestHead, user);

+    }

+

+    private Response updateTestHeadFields(TestHead testHead, TestHead newTestHead, User user) {

+        Query select = Query.query(Criteria.where("_id").is(testHead.get_id()));

+        Update update = new Update();

+

+        if (newTestHead.getTestHeadName() != null) {

+            if (doesTestHeadWithNameExist(newTestHead.getTestHeadName())) {

+                String error =

+                        String.format(

+                                "Cant change testHeadName to %s since it already exists.",

+                                newTestHead.getTestHeadName());

+                return ResponseUtility.Build.badRequestWithMessage(error);

+            }

+            testHead.setTestHeadName(newTestHead.getTestHeadName());

+            update.set("testHeadName", newTestHead.getTestHeadName());

+        }

+        if (newTestHead.getTestHeadDescription() != null) {

+            testHead.setTestHeadDescription(newTestHead.getTestHeadDescription());

+            update.set("testHeadDescription", newTestHead.getTestHeadDescription());

+        }

+        if (newTestHead.getHostname() != null) {

+            testHead.setHostname(newTestHead.getHostname());

+            update.set("hostname", newTestHead.getHostname());

+        }

+        if (newTestHead.getPort() != null) {

+            testHead.setPort(newTestHead.getPort());

+            update.set("port", newTestHead.getPort());

+        }

+        if (newTestHead.getResourcePath() != null) {

+            testHead.setResourcePath(newTestHead.getResourcePath());

+            update.set("resourcePath", newTestHead.getResourcePath());

+        }

+        if (newTestHead.getAuthorizationType() != null) {

+            testHead.setAuthorizationType(newTestHead.getAuthorizationType());

+            update.set("authorizationType", newTestHead.getAuthorizationType());

+        }

+        if (newTestHead.getAuthorizationCredential() != null) {

+            testHead.setAuthorizationCredential(newTestHead.getAuthorizationCredential());

+            update.set("authorizationCredential", newTestHead.getAuthorizationCredential());

+        }

+        if (newTestHead.getAuthorizationEnabled() != null) {

+            testHead.setAuthorizationEnabled(newTestHead.getAuthorizationEnabled());

+            update.set("authorizationEnabled", newTestHead.getAuthorizationEnabled());

+        }

+        if (newTestHead.getVthInputTemplate() != null) {

+            testHead.setVthInputTemplate(newTestHead.getVthInputTemplate());

+            update.set("vthInputTemplate", newTestHead.getVthInputTemplate());

+        }

+        testHead.setUpdatedAt(new Date());

+        update.set("updatedAt", testHead.getUpdatedAt());

+        testHead.setUpdatedBy(user.get_id());

+        update.set("updatedBy", user.get_id());

+

+        mongoTemplate.updateFirst(select, update, "testHeads");

+        return ResponseUtility.Build.okRequestWithObject(testHead);

+    }

+

+    // check if test head exists in database by name

+    private boolean doesTestHeadWithNameExist(String name) {

+        Optional<TestHead> optionalTestHead =

+                testHeadRepository.findByTestHeadName(name);

+        return optionalTestHead.isPresent();

+    }

+}

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
index 0000000..9214407
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/Group.java
@@ -0,0 +1,110 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.List;

+

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "groups")

+public class Group implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  @Id

+  private ObjectId _id;

+  private String groupName;

+  private String groupDescription;

+  private List<ObjectId> mechanizedIds;

+  private ObjectId ownerId;

+  private List<Role> roles;

+  private List<GroupMember> members;

+  private ObjectId parentGroupId;

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getGroupName() {

+    return groupName;

+  }

+

+  public void setGroupName(String groupName) {

+    this.groupName = groupName;

+  }

+

+  public String getGroupDescription() {

+    return groupDescription;

+  }

+

+  public void setGroupDescription(String groupDescription) {

+    this.groupDescription = groupDescription;

+  }

+

+  public List<ObjectId> getMechanizedIds() {

+    return mechanizedIds;

+  }

+

+  public void setMechanizedIds(List<ObjectId> mechanizedIds) {

+    this.mechanizedIds = mechanizedIds;

+  }

+

+  public ObjectId getOwnerId() {

+    return ownerId;

+  }

+

+  public void setOwnerId(ObjectId ownerId) {

+    this.ownerId = ownerId;

+  }

+

+  public List<Role> getRoles() {

+    return roles;

+  }

+

+  public void setRoles(List<Role> roles) {

+    this.roles = roles;

+  }

+

+    public List<GroupMember> getMembers() {

+    return members;

+  }

+

+  public void setMembers(List<GroupMember> members) {

+    this.members = members;

+  }

+

+  public ObjectId getParentGroupId() {

+    return parentGroupId;

+  }

+

+  public void setParentGroupId(ObjectId parentGroupId) {

+    this.parentGroupId = parentGroupId;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..583c213
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/GroupMember.java
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.bson.types.ObjectId;

+

+import java.util.List;

+import java.util.Map;

+

+public class GroupMember {

+    private ObjectId userId;

+    private List<String> roles;//this is name of roles assigned to user that are created within the group i.e admin,dev,.. etc

+

+    public ObjectId getUserId() {

+        return userId;

+    }

+

+    public void setUserId(ObjectId userId) {

+        this.userId = userId;

+    }

+

+    public List<String> getRoles() {

+        return roles;

+    }

+

+    public void setRoles(List<String> roles) {

+        this.roles = roles;

+    }

+}

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
index 0000000..aca09f1
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/Role.java
@@ -0,0 +1,41 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import java.util.List;

+

+public class Role {

+

+    private String roleName;

+    private List<String> permissions;

+

+    public String getRoleName() {

+        return roleName;

+    }

+

+    public void setRoleName(String roleName) {

+        this.roleName = roleName;

+    }

+

+    public List<String> getPermissions() {

+        return permissions;

+    }

+

+    public void setPermissions(List<String> permissions) {

+        this.permissions = permissions;

+    }

+}

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
index 0000000..2a66fa2
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/TestDefinition.java
@@ -0,0 +1,137 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.List;

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "testDefinitions")

+public class TestDefinition implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  @Id private ObjectId _id;

+  private String testName;

+  private String testDescription;

+  private String processDefinitionKey;

+  private List<BpmnInstance> bpmnInstances;

+  private ObjectId groupId;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+  private boolean disabled;

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestName() {

+    return testName;

+  }

+

+  public void setTestName(String testName) {

+    this.testName = testName;

+  }

+

+  public String getTestDescription() {

+    return testDescription;

+  }

+

+  public void setTestDescription(String testDescription) {

+    this.testDescription = testDescription;

+  }

+

+  public String getProcessDefinitionKey() {

+    return processDefinitionKey;

+  }

+

+  public void setProcessDefinitionKey(String processDefinitionKey) {

+    this.processDefinitionKey = processDefinitionKey;

+  }

+

+  public List<BpmnInstance> getBpmnInstances() {

+    return bpmnInstances;

+  }

+

+  public void setBpmnInstances(List<BpmnInstance> bpmnInstances) {

+    this.bpmnInstances = bpmnInstances;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  public boolean isDisabled() {

+    return disabled;

+  }

+

+  public void setDisabled(boolean disabled) {

+    this.disabled = disabled;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..8f02e0b
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/TestExecution.java
@@ -0,0 +1,245 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.model.historic.TestDefinitionHistoric;

+import org.oran.otf.common.model.historic.TestInstanceHistoric;

+import org.oran.otf.common.model.local.TestHeadResult;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.List;

+import java.util.Map;

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "testExecutions")

+public class TestExecution implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  @Id

+  private ObjectId _id;

+  private ObjectId groupId;

+  private ObjectId executorId;

+

+  private boolean async;

+  private Date startTime;

+  private Date endTime;

+  private String asyncTopic;

+  private String businessKey;

+  private String processInstanceId;

+  private String testResult;

+  private String testResultMessage;

+  private Map<String, Object> testDetails;

+  private List<TestHeadResult> testHeadResults;

+  private List<TestExecution> testInstanceResults;

+  // Stores historic information of associated

+  private String historicEmail;

+  private TestInstanceHistoric historicTestInstance;

+  private TestDefinitionHistoric historicTestDefinition;

+

+  public TestExecution() {

+  }

+

+  public TestExecution(

+          ObjectId _id,

+          ObjectId groupId,

+          ObjectId executorId,

+          boolean async,

+          Date startTime,

+          Date endTime,

+          String asyncTopic,

+          String businessKey,

+          String processInstanceId,

+          String testResult,

+          String testResultMessage,

+          Map<String, Object> testDetails,

+          List<TestHeadResult> testHeadResults,

+          List<TestExecution> testInstanceResults,

+          String historicEmail,

+          TestInstanceHistoric historicTestInstance,

+          TestDefinitionHistoric historicTestDefinition) {

+    this._id = _id;

+    this.groupId = groupId;

+    this.executorId = executorId;

+    this.async = async;

+    this.startTime = startTime;

+    this.endTime = endTime;

+    this.asyncTopic = asyncTopic;

+    this.businessKey = businessKey;

+    this.processInstanceId = processInstanceId;

+    this.testResult = testResult;

+    this.testDetails = testDetails;

+    this.testHeadResults = testHeadResults;

+    this.testInstanceResults = testInstanceResults;

+    this.historicEmail = historicEmail;

+    this.historicTestInstance = historicTestInstance;

+    this.historicTestDefinition = historicTestDefinition;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public ObjectId getExecutorId() {

+    return executorId;

+  }

+

+  public void setExecutorId(ObjectId executorId) {

+    this.executorId = executorId;

+  }

+

+  public boolean isAsync() {

+    return async;

+  }

+

+  public void setAsync(boolean async) {

+    this.async = async;

+  }

+

+  public Date getStartTime() {

+    return startTime;

+  }

+

+  public void setStartTime(Date startTime) {

+    this.startTime = startTime;

+  }

+

+  public Date getEndTime() {

+    return endTime;

+  }

+

+  public void setEndTime(Date endTime) {

+    this.endTime = endTime;

+  }

+

+  public String getAsyncTopic() {

+    return asyncTopic;

+  }

+

+  public void setAsyncTopic(String asyncTopic) {

+    this.asyncTopic = asyncTopic;

+  }

+

+  public String getBusinessKey() {

+    return businessKey;

+  }

+

+  public void setBusinessKey(String businessKey) {

+    this.businessKey = businessKey;

+  }

+

+  public String getProcessInstanceId() {

+    return processInstanceId;

+  }

+

+  public void setProcessInstanceId(String processInstanceId) {

+    this.processInstanceId = processInstanceId;

+  }

+

+  public String getTestResult() {

+    return testResult;

+  }

+

+  public void setTestResult(String testResult) {

+    this.testResult = testResult;

+  }

+

+  public String getTestResultMessage() {

+    return testResultMessage;

+  }

+

+  public void setTestResultMessage(String testResultMessage) {

+    this.testResultMessage = testResultMessage;

+  }

+

+  public Map<String, Object> getTestDetails() {

+    return testDetails;

+  }

+

+  public void setTestDetails(Map<String, Object> testDetails) {

+    this.testDetails = testDetails;

+  }

+

+  public List<TestHeadResult> getTestHeadResults() {

+    synchronized (testHeadResults) {

+      return testHeadResults;

+    }

+  }

+

+  public void setTestHeadResults(List<TestHeadResult> testHeadResults) {

+    synchronized (testHeadResults) {

+      this.testHeadResults = testHeadResults;

+    }

+  }

+

+  public List<TestExecution> getTestInstanceResults() {

+    synchronized (testInstanceResults) {

+      return testInstanceResults;

+    }

+  }

+

+  public void setTestInstanceResults(List<TestExecution> testInstanceResults) {

+    synchronized (testInstanceResults) {

+      this.testInstanceResults = testInstanceResults;

+    }

+  }

+

+  public String getHistoricEmail() {

+    return historicEmail;

+  }

+

+  public void setHistoricEmail(String historicEmail) {

+    this.historicEmail = historicEmail;

+  }

+

+  public TestInstanceHistoric getHistoricTestInstance() {

+    return historicTestInstance;

+  }

+

+  public void setHistoricTestInstance(TestInstanceHistoric historicTestInstance) {

+    this.historicTestInstance = historicTestInstance;

+  }

+

+  public TestDefinitionHistoric getHistoricTestDefinition() {

+    return historicTestDefinition;

+  }

+

+  public void setHistoricTestDefinition(

+          TestDefinitionHistoric historicTestDefinition) {

+    this.historicTestDefinition = historicTestDefinition;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..7f4bcbc
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/TestHead.java
@@ -0,0 +1,224 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.Map;

+

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.index.Indexed;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "testHeads")

+public class TestHead implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  @Id

+  private ObjectId _id;

+

+  @Indexed(unique = true)

+  private String testHeadName;

+

+  private String testHeadDescription;

+  private String hostname;

+  private String port;

+  private String resourcePath;

+  private ObjectId creatorId;

+  private ObjectId groupId;

+  private String authorizationType;

+  private String authorizationCredential;

+  private Boolean authorizationEnabled;

+  private Map<String, Object> vthInputTemplate;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId updatedBy;

+  private Boolean isPublic;

+  public TestHead() {

+  }

+

+  public TestHead(

+          ObjectId _id,

+          String testHeadName,

+          String testHeadDescription,

+          String hostname,

+          String port,

+          String resourcePath,

+          ObjectId creatorId,

+          ObjectId groupId,

+          String authorizationType,

+          String authorizationCredential,

+          boolean authorizationEnabled,

+          Map<String, Object> vthInputTemplate,

+          Date createdAt,

+          Date updatedAt,

+          ObjectId updatedBy,

+          Boolean isPublic) {

+    this._id = _id;

+    this.testHeadName = testHeadName;

+    this.testHeadDescription = testHeadDescription;

+    this.hostname = hostname;

+    this.port = port;

+    this.resourcePath = resourcePath;

+    this.creatorId = creatorId;

+    this.groupId = groupId;

+    this.authorizationType = authorizationType;

+    this.authorizationCredential = authorizationCredential;

+    this.authorizationEnabled = authorizationEnabled;

+    this.vthInputTemplate = vthInputTemplate;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.updatedBy = updatedBy;

+    this.isPublic = isPublic;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestHeadName() {

+    return testHeadName;

+  }

+

+  public void setTestHeadName(String testHeadName) {

+    this.testHeadName = testHeadName;

+  }

+

+  public String getTestHeadDescription() {

+    return testHeadDescription;

+  }

+

+  public void setTestHeadDescription(String testHeadDescription) {

+    this.testHeadDescription = testHeadDescription;

+  }

+

+  public String getHostname() {

+    return hostname;

+  }

+

+  public void setHostname(String hostname) {

+    this.hostname = hostname;

+  }

+

+  public String getPort() {

+    return port;

+  }

+

+  public void setPort(String port) {

+    this.port = port;

+  }

+

+  public String getResourcePath() {

+    return resourcePath;

+  }

+

+  public void setResourcePath(String resourcePath) {

+    this.resourcePath = resourcePath;

+  }

+

+  public ObjectId getCreatorId() {

+    return creatorId;

+  }

+

+  public void setCreatorId(ObjectId creatorId) {

+    this.creatorId = creatorId;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public String getAuthorizationCredential() {

+    return authorizationCredential;

+  }

+

+  public String getAuthorizationType() {

+    return authorizationType;

+  }

+

+  public void setAuthorizationType(String authorizationType) {

+    this.authorizationType = authorizationType;

+  }

+

+  public void setAuthorizationCredential(String authorizationCredential) {

+    this.authorizationCredential = authorizationCredential;

+  }

+

+  public Boolean getAuthorizationEnabled() {

+    return authorizationEnabled;

+  }

+

+  public void setAuthorizationEnabled(Boolean authorizationEnabled) {

+    this.authorizationEnabled = authorizationEnabled;

+  }

+

+  public Map<String, Object> getVthInputTemplate() {

+    return vthInputTemplate;

+  }

+

+  public void setVthInputTemplate(Map<String, Object> vthInputTemplate) {

+    this.vthInputTemplate = vthInputTemplate;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  public Boolean isPublic() {

+    return isPublic;

+  }

+

+  public void setPublic(Boolean aPublic) {

+    isPublic = aPublic;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..9b11fa4
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/TestInstance.java
@@ -0,0 +1,256 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.model.local.ParallelFlowInput;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.HashMap;

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "testInstances")

+public class TestInstance implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private @Id ObjectId _id;

+  private String testInstanceName;

+  private String testInstanceDescription;

+  private ObjectId groupId;

+  private ObjectId testDefinitionId;

+  private String processDefinitionId;

+  private boolean useLatestTestDefinition;

+  private boolean disabled;

+  private boolean simulationMode;

+  private long maxExecutionTimeInMillis;

+  private HashMap<String, ParallelFlowInput> pfloInput;

+  private HashMap<String, Object> internalTestData;

+  private HashMap<String, Object> simulationVthInput;

+  private HashMap<String, Object> testData;

+  private HashMap<String, Object> vthInput;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+

+  public TestInstance() {}

+

+  public TestInstance(

+      ObjectId _id,

+      String testInstanceName,

+      String testInstanceDescription,

+      ObjectId groupId,

+      ObjectId testDefinitionId,

+      String processDefinitionId,

+      boolean useLatestTestDefinition,

+      boolean disabled,

+      boolean simulationMode,

+      long maxExecutionTimeInMillis,

+      HashMap<String, ParallelFlowInput> pfloInput,

+      HashMap<String, Object> internalTestData,

+      HashMap<String, Object> simulationVthInput,

+      HashMap<String, Object> testData,

+      HashMap<String, Object> vthInput,

+      Date createdAt,

+      Date updatedAt,

+      ObjectId createdBy,

+      ObjectId updatedBy) {

+    this._id = _id;

+    this.testInstanceName = testInstanceName;

+    this.testInstanceDescription = testInstanceDescription;

+    this.groupId = groupId;

+    this.testDefinitionId = testDefinitionId;

+    this.processDefinitionId = processDefinitionId;

+    this.useLatestTestDefinition = useLatestTestDefinition;

+    this.disabled = disabled;

+    this.simulationMode = simulationMode;

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+    this.pfloInput = pfloInput;

+    this.internalTestData = internalTestData;

+    this.simulationVthInput = simulationVthInput;

+    this.testData = testData;

+    this.vthInput = vthInput;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.createdBy = createdBy;

+    this.updatedBy = updatedBy;

+  }

+

+  public static long getSerialVersionUID() {

+    return serialVersionUID;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestInstanceName() {

+    return testInstanceName;

+  }

+

+  public void setTestInstanceName(String testInstanceName) {

+    this.testInstanceName = testInstanceName;

+  }

+

+  public String getTestInstanceDescription() {

+    return testInstanceDescription;

+  }

+

+  public void setTestInstanceDescription(String testInstanceDescription) {

+    this.testInstanceDescription = testInstanceDescription;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public ObjectId getTestDefinitionId() {

+    return testDefinitionId;

+  }

+

+  public void setTestDefinitionId(ObjectId testDefinitionId) {

+    this.testDefinitionId = testDefinitionId;

+  }

+

+  public String getProcessDefinitionId() {

+    return processDefinitionId;

+  }

+

+  public void setProcessDefinitionId(String processDefinitionId) {

+    this.processDefinitionId = processDefinitionId;

+  }

+

+  public boolean isUseLatestTestDefinition() {

+    return useLatestTestDefinition;

+  }

+

+  public void setUseLatestTestDefinition(boolean useLatestTestDefinition) {

+    this.useLatestTestDefinition = useLatestTestDefinition;

+  }

+

+  public boolean isDisabled() {

+    return disabled;

+  }

+

+  public void setDisabled(boolean disabled) {

+    this.disabled = disabled;

+  }

+

+  public boolean isSimulationMode() {

+    return simulationMode;

+  }

+

+  public void setSimulationMode(boolean simulationMode) {

+    this.simulationMode = simulationMode;

+  }

+

+  public long getMaxExecutionTimeInMillis() {

+    return maxExecutionTimeInMillis;

+  }

+

+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+  }

+

+  public HashMap<String, ParallelFlowInput> getPfloInput() {

+    return pfloInput;

+  }

+

+  public void setPfloInput(HashMap<String, ParallelFlowInput> pfloInput) {

+    this.pfloInput = pfloInput;

+  }

+

+  public HashMap<String, Object> getInternalTestData() {

+    return internalTestData;

+  }

+

+  public void setInternalTestData(HashMap<String, Object> internalTestData) {

+    this.internalTestData = internalTestData;

+  }

+

+  public HashMap<String, Object> getSimulationVthInput() {

+    return simulationVthInput;

+  }

+

+  public void setSimulationVthInput(HashMap<String, Object> simulationVthInput) {

+    this.simulationVthInput = simulationVthInput;

+  }

+

+  public HashMap<String, Object> getTestData() {

+    return testData;

+  }

+

+  public void setTestData(HashMap<String, Object> testData) {

+    this.testData = testData;

+  }

+

+  public HashMap<String, Object> getVthInput() {

+    return vthInput;

+  }

+

+  public void setVthInput(HashMap<String, Object> vthInput) {

+    this.vthInput = vthInput;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..2c56b85
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/User.java
@@ -0,0 +1,142 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model;

+

+import org.oran.otf.common.model.local.UserGroup;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.List;

+import org.bson.types.ObjectId;

+import org.springframework.data.mongodb.core.mapping.Document;

+

+@Document(collection = "users")

+public class User implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId _id;

+  private List<String> permissions;

+  private String firstName;

+  private String lastName;

+  private String email;

+  private String password;

+  private List<UserGroup> groups;

+  private Date createdAt;

+  private Date updatedAt;

+

+  //Added User for testing

+  public User(){};

+

+  public User(

+      ObjectId _id,

+      List<String> permissions,

+      String firstName,

+      String lastName,

+      String email,

+      String password,

+      List<UserGroup> groups,

+      Date createdAt,

+      Date updatedAt) {

+    this._id = _id;

+    this.permissions = permissions;

+    this.firstName = firstName;

+    this.lastName = lastName;

+    this.email = email;

+    this.password = password;

+    this.groups = groups;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public List<String> getPermissions() {

+    return permissions;

+  }

+

+  public void setPermissions(List<String> permissions) {

+    this.permissions = permissions;

+  }

+

+  public String getFirstName() {

+    return firstName;

+  }

+

+  public void setFirstName(String firstName) {

+    this.firstName = firstName;

+  }

+

+  public String getLastName() {

+    return lastName;

+  }

+

+  public void setLastName(String lastName) {

+    this.lastName = lastName;

+  }

+

+  public String getEmail() {

+    return email;

+  }

+

+  public void setEmail(String email) {

+    this.email = email;

+  }

+

+  public String getPassword() {

+    return password;

+  }

+

+  public void setPassword(String password) {

+    this.password = password;

+  }

+

+  public List<UserGroup> getGroups() {

+    return groups;

+  }

+

+  public void setGroups(List<UserGroup> groups) {

+    this.groups = groups;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..fe2be4b
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/historic/TestDefinitionHistoric.java
@@ -0,0 +1,185 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.historic;

+

+import org.oran.otf.common.model.TestDefinition;

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.ArrayList;

+import java.util.Date;

+import java.util.List;

+import org.bson.types.ObjectId;

+

+public class TestDefinitionHistoric implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId _id;

+  private String testName;

+  private String testDescription;

+  private String processDefinitionKey;

+  private List<BpmnInstance> bpmnInstances;

+  private ObjectId groupId;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+

+  public TestDefinitionHistoric() {

+  }

+

+  public TestDefinitionHistoric(TestDefinition testDefinition, String processDefinitionId) {

+    this._id = testDefinition.get_id();

+    this.testName = testDefinition.getTestName();

+    this.testDescription = testDefinition.getTestDescription();

+    this.processDefinitionKey = testDefinition.getProcessDefinitionKey();

+    this.bpmnInstances =

+        getHistoricBpmnInstanceAsList(testDefinition.getBpmnInstances(), processDefinitionId);

+    this.groupId = testDefinition.getGroupId();

+    this.createdAt = testDefinition.getCreatedAt();

+    this.updatedAt = testDefinition.getUpdatedAt();

+    this.createdBy = testDefinition.getCreatedBy();

+    this.updatedBy = testDefinition.getUpdatedBy();

+  }

+

+  public TestDefinitionHistoric(

+      ObjectId _id,

+      String testName,

+      String testDescription,

+      String processDefinitionKey,

+      List<BpmnInstance> bpmnInstances,

+      ObjectId groupId,

+      Date createdAt,

+      Date updatedAt,

+      ObjectId createdBy,

+      ObjectId updatedBy) {

+    this._id = _id;

+    this.testName = testName;

+    this.testDescription = testDescription;

+    this.processDefinitionKey = processDefinitionKey;

+    this.bpmnInstances = bpmnInstances;

+    this.groupId = groupId;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.createdBy = createdBy;

+    this.updatedBy = updatedBy;

+  }

+

+  private List<BpmnInstance> getHistoricBpmnInstanceAsList(

+      List<BpmnInstance> bpmnInstances, String processDefinitionId) {

+    BpmnInstance bpmnInstance =

+        bpmnInstances.stream()

+            .filter(

+                _bpmnInstance ->

+                    _bpmnInstance.getProcessDefinitionId().equalsIgnoreCase(processDefinitionId))

+            .findFirst()

+            .orElse(null);

+

+    List<BpmnInstance> historicBpmnInstance = new ArrayList<>();

+    if (bpmnInstance != null) {

+      historicBpmnInstance.add(bpmnInstance);

+    }

+

+    return historicBpmnInstance;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestName() {

+    return testName;

+  }

+

+  public void setTestName(String testName) {

+    this.testName = testName;

+  }

+

+  public String getTestDescription() {

+    return testDescription;

+  }

+

+  public void setTestDescription(String testDescription) {

+    this.testDescription = testDescription;

+  }

+

+  public String getProcessDefinitionKey() {

+    return processDefinitionKey;

+  }

+

+  public void setProcessDefinitionKey(String processDefinitionKey) {

+    this.processDefinitionKey = processDefinitionKey;

+  }

+

+  public List<BpmnInstance> getBpmnInstances() {

+    return bpmnInstances;

+  }

+

+  public void setBpmnInstances(List<BpmnInstance> bpmnInstances) {

+    this.bpmnInstances = bpmnInstances;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..1263893
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/historic/TestInstanceHistoric.java
@@ -0,0 +1,234 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.historic;

+

+import org.oran.otf.common.model.TestInstance;

+import org.oran.otf.common.model.local.ParallelFlowInput;

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.HashMap;

+import java.util.Map;

+import org.bson.types.ObjectId;

+import org.springframework.data.annotation.Id;

+

+public class TestInstanceHistoric implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private @Id

+  ObjectId _id;

+  private String testInstanceName;

+  private String testInstanceDescription;

+  private ObjectId groupId;

+  private ObjectId testDefinitionId;

+  private String processDefinitionId;

+  private Map<String, ParallelFlowInput> pfloInput;

+  private Map<String, Object> simulationVthInput;

+  private Map<String, Object> testData;

+  private Map<String, Object> vthInput;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+  private boolean simulationMode;

+

+  public TestInstanceHistoric() {

+  }

+

+  public TestInstanceHistoric(TestInstance testInstance) {

+    this._id = testInstance.get_id();

+    this.testInstanceName = testInstance.getTestInstanceName();

+    this.testInstanceDescription = testInstance.getTestInstanceDescription();

+    this.groupId = testInstance.getGroupId();

+    this.testDefinitionId = testInstance.getTestDefinitionId();

+    this.pfloInput = testInstance.getPfloInput();

+    this.processDefinitionId = testInstance.getProcessDefinitionId();

+    this.simulationVthInput = testInstance.getSimulationVthInput();

+    this.testData = testInstance.getTestData();

+    this.vthInput = testInstance.getVthInput();

+    this.createdAt = testInstance.getCreatedAt();

+    this.updatedAt = testInstance.getUpdatedAt();

+    this.createdBy = testInstance.getCreatedBy();

+    this.updatedBy = testInstance.getUpdatedBy();

+    this.simulationMode = testInstance.isSimulationMode();

+  }

+

+  public TestInstanceHistoric(

+      ObjectId _id,

+      String testInstanceName,

+      String testInstanceDescription,

+      ObjectId groupId,

+      ObjectId testDefinitionId,

+      String processDefinitionId,

+      HashMap<String, ParallelFlowInput> pfloInput,

+      HashMap<String, Object> simulationVthInput,

+      HashMap<String, Object> testData,

+      HashMap<String, Object> vthInput,

+      Date createdAt,

+      Date updatedAt,

+      ObjectId createdBy,

+      ObjectId updatedBy,

+      boolean simulationMode) {

+    this._id = _id;

+    this.testInstanceName = testInstanceName;

+    this.testInstanceDescription = testInstanceDescription;

+    this.groupId = groupId;

+    this.testDefinitionId = testDefinitionId;

+    this.processDefinitionId = processDefinitionId;

+    this.pfloInput = pfloInput;

+    this.simulationVthInput = simulationVthInput;

+    this.testData = testData;

+    this.vthInput = vthInput;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.createdBy = createdBy;

+    this.updatedBy = updatedBy;

+    this.simulationMode = simulationMode;

+  }

+

+  public static long getSerialVersionUID() {

+    return serialVersionUID;

+  }

+

+  public ObjectId get_id() {

+    return _id;

+  }

+

+  public void set_id(ObjectId _id) {

+    this._id = _id;

+  }

+

+  public String getTestInstanceName() {

+    return testInstanceName;

+  }

+

+  public void setTestInstanceName(String testInstanceName) {

+    this.testInstanceName = testInstanceName;

+  }

+

+  public String getTestInstanceDescription() {

+    return testInstanceDescription;

+  }

+

+  public void setTestInstanceDescription(String testInstanceDescription) {

+    this.testInstanceDescription = testInstanceDescription;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public ObjectId getTestDefinitionId() {

+    return testDefinitionId;

+  }

+

+  public void setTestDefinitionId(ObjectId testDefinitionId) {

+    this.testDefinitionId = testDefinitionId;

+  }

+

+  public String getProcessDefinitionId() {

+    return processDefinitionId;

+  }

+

+  public void setProcessDefinitionId(String processDefinitionId) {

+    this.processDefinitionId = processDefinitionId;

+  }

+

+  public Map<String, ParallelFlowInput> getPfloInput() {

+    return pfloInput;

+  }

+

+  public void setPfloInput(

+      HashMap<String, ParallelFlowInput> pfloInput) {

+    this.pfloInput = pfloInput;

+  }

+

+  public Map<String, Object> getSimulationVthInput() {

+    return simulationVthInput;

+  }

+

+  public void setSimulationVthInput(

+      HashMap<String, Object> simulationVthInput) {

+    this.simulationVthInput = simulationVthInput;

+  }

+

+  public Map<String, Object> getTestData() {

+    return testData;

+  }

+

+  public void setTestData(HashMap<String, Object> testData) {

+    this.testData = testData;

+  }

+

+  public Map<String, Object> getVthInput() {

+    return vthInput;

+  }

+

+  public void setVthInput(HashMap<String, Object> vthInput) {

+    this.vthInput = vthInput;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  public boolean isSimulationMode() {

+    return simulationMode;

+  }

+

+  public void setSimulationMode(boolean simulationMode) {

+    this.simulationMode = simulationMode;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..05ec6a9
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/ApiRequest.java
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+public class ApiRequest {}

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
index 0000000..c4440b0
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/BpmnInstance.java
@@ -0,0 +1,191 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import com.fasterxml.jackson.annotation.JsonCreator;

+import com.fasterxml.jackson.annotation.JsonProperty;

+import java.io.Serializable;

+import java.util.Date;

+import java.util.List;

+import java.util.Map;

+import org.bson.types.ObjectId;

+

+public class BpmnInstance implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private String processDefinitionId;

+  private String deploymentId;

+  private int version;

+  private ObjectId bpmnFileId;

+  private ObjectId resourceFileId;

+  private boolean isDeployed;

+  private List<TestHeadNode> testHeads;

+  private List<PfloNode> pflos;

+  private Map<String, Object> testDataTemplate;

+  private Date createdAt;

+  private Date updatedAt;

+  private ObjectId createdBy;

+  private ObjectId updatedBy;

+

+  public BpmnInstance() {

+  }

+

+  @JsonCreator

+  public BpmnInstance(

+      @JsonProperty("processDefinitionId") String processDefinitionId,

+      @JsonProperty("deploymentId") String deploymentId,

+      @JsonProperty("version") int version,

+      @JsonProperty("bpmnFileId") ObjectId bpmnFileId,

+      @JsonProperty("resourceFileId") ObjectId resourceFileId,

+      @JsonProperty("isDeployed") boolean isDeployed,

+      @JsonProperty("testHeads") List<TestHeadNode> testHeads,

+      @JsonProperty("plfos") List<PfloNode> pflos,

+      @JsonProperty("testDataTemplate") Map<String, Object> testDataTemplate,

+      @JsonProperty("createdAt") Date createdAt,

+      @JsonProperty("updateAt") Date updatedAt,

+      @JsonProperty("createdBy") ObjectId createdBy,

+      @JsonProperty("updatedBy") ObjectId updatedBy) {

+    this.processDefinitionId = processDefinitionId;

+    this.deploymentId = deploymentId;

+    this.version = version;

+    this.bpmnFileId = bpmnFileId;

+    this.resourceFileId = resourceFileId;

+    this.isDeployed = isDeployed;

+    this.testHeads = testHeads;

+    this.testDataTemplate = testDataTemplate;

+    this.createdAt = createdAt;

+    this.updatedAt = updatedAt;

+    this.createdBy = createdBy;

+    this.updatedBy = updatedBy;

+  }

+

+  public String getProcessDefinitionId() {

+    return processDefinitionId;

+  }

+

+  public void setProcessDefinitionId(String processDefinitionId) {

+    this.processDefinitionId = processDefinitionId;

+  }

+

+  public String getDeploymentId() {

+    return deploymentId;

+  }

+

+  public void setDeploymentId(String deploymentId) {

+    this.deploymentId = deploymentId;

+  }

+

+  public int getVersion() {

+    return version;

+  }

+

+  public void setVersion(int version) {

+    this.version = version;

+  }

+

+  public ObjectId getBpmnFileId() {

+    return bpmnFileId;

+  }

+

+  public void setBpmnFileId(ObjectId bpmnFileId) {

+    this.bpmnFileId = bpmnFileId;

+  }

+

+  public ObjectId getResourceFileId() {

+    return resourceFileId;

+  }

+

+  public void setResourceFileId(ObjectId resourceFileId) {

+    this.resourceFileId = resourceFileId;

+  }

+

+  @JsonProperty(value="isDeployed")

+  public boolean isDeployed() {

+    return isDeployed;

+  }

+

+  public void setDeployed(boolean deployed) {

+    isDeployed = deployed;

+  }

+

+  public List<TestHeadNode> getTestHeads() {

+    return testHeads;

+  }

+

+  public void setTestHeads(List<TestHeadNode> testHeads) {

+    this.testHeads = testHeads;

+  }

+

+  public List<PfloNode> getPflos() {

+    return pflos;

+  }

+

+  public void setPflos(List<PfloNode> pflos) {

+    this.pflos = pflos;

+  }

+

+  public Map<String, Object> getTestDataTemplate() {

+    return testDataTemplate;

+  }

+

+  public void setTestDataTemplate(Map<String, Object> testDataTemplate) {

+    this.testDataTemplate = testDataTemplate;

+  }

+

+  public Date getCreatedAt() {

+    return createdAt;

+  }

+

+  public void setCreatedAt(Date createdAt) {

+    this.createdAt = createdAt;

+  }

+

+  public Date getUpdatedAt() {

+    return updatedAt;

+  }

+

+  public void setUpdatedAt(Date updatedAt) {

+    this.updatedAt = updatedAt;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public ObjectId getUpdatedBy() {

+    return updatedBy;

+  }

+

+  public void setUpdatedBy(ObjectId updatedBy) {

+    this.updatedBy = updatedBy;

+  }

+

+  private String getObjectIdString(ObjectId value) {

+    return value == null ? "\"\"" : "\"" + value.toString() + "\"";

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..16040e7
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/DeployTestStrategyRequest.java
@@ -0,0 +1,73 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.bson.types.ObjectId;

+

+public class DeployTestStrategyRequest {

+  private ObjectId testDefinitionDeployerId;

+  private ObjectId testDefinitionId;

+  private String definitionId;

+

+  public DeployTestStrategyRequest() {}

+

+  public DeployTestStrategyRequest(

+      ObjectId testDefinitionDeployerId, ObjectId testDefinitionId, String definitionId) {

+    this.testDefinitionDeployerId = testDefinitionDeployerId;

+    this.testDefinitionId = testDefinitionId;

+    this.definitionId = definitionId;

+  }

+

+  public ObjectId getTestDefinitionDeployerId() {

+    return testDefinitionDeployerId;

+  }

+

+  public void setTestDefinitionDeployerId(ObjectId testDefinitionDeployerId) {

+    this.testDefinitionDeployerId = testDefinitionDeployerId;

+  }

+

+  public ObjectId getTestDefinitionId() {

+    return testDefinitionId;

+  }

+

+  public void setTestDefinitionId(ObjectId testDefinitionId) {

+    this.testDefinitionId = testDefinitionId;

+  }

+

+  public String getDefinitionId() {

+    return definitionId;

+  }

+

+  public void setDefinitionId(String definitionId) {

+    this.definitionId = definitionId;

+  }

+

+  @Override

+  public String toString() {

+    return "{\"DeployTestStrategyRequest\":{"

+        + "\"testDefinitionDeployerId\":\""

+        + testDefinitionDeployerId

+        + "\""

+        + ", \"testDefinitionId\":\""

+        + testDefinitionId

+        + "\""

+        + ", \"definitionId\":\""

+        + definitionId

+        + "\""

+        + "}}";

+  }

+}

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
index 0000000..e4f959e
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/OTFApiResponse.java
@@ -0,0 +1,66 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+

+import java.util.Date;

+

+public class OTFApiResponse {

+

+    private int statusCode;

+    private String message;

+    private Date time;

+

+    public OTFApiResponse() {

+    }

+

+    public OTFApiResponse(int statusCode, String message) {

+        this.statusCode = statusCode;

+        this.message = message;

+        this.time = new Date(System.currentTimeMillis());

+    }

+

+    public int getStatusCode() {

+        return statusCode;

+    }

+

+    public void setStatusCode(int statusCode) {

+        this.statusCode = statusCode;

+    }

+

+    public String getMessage() {

+        return message;

+    }

+

+    public void setMessage(String message) {

+        this.message = message;

+    }

+

+    public Date getTime() {

+        return time;

+    }

+

+    public void setTime(Date time) {

+        this.time = time;

+    }

+

+    @Override

+    public String toString() {

+        return Convert.objectToJson(this);

+    }

+}

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
index 0000000..2ac94e1
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/ParallelFlowInput.java
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+

+import java.io.Serializable;

+import java.util.List;

+

+public class ParallelFlowInput implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private List<WorkflowRequest> args;

+  private boolean interruptOnFailure;

+  private int maxFailures;

+  private int threadPoolSize;

+

+  public ParallelFlowInput() {}

+

+  public ParallelFlowInput(

+      List<WorkflowRequest> args, boolean interruptOnFailure, int maxFailures, int threadPoolSize) {

+    this.args = args;

+    this.interruptOnFailure = interruptOnFailure;

+    this.maxFailures = maxFailures;

+    this.threadPoolSize = threadPoolSize;

+  }

+

+  public static long getSerialVersionUID() {

+    return serialVersionUID;

+  }

+

+  public List<WorkflowRequest> getArgs() {

+    return args;

+  }

+

+  public void setArgs(List<WorkflowRequest> args) {

+    this.args = args;

+  }

+

+  public boolean isInterruptOnFailure() {

+    return interruptOnFailure;

+  }

+

+  public void setInterruptOnFailure(boolean interruptOnFailure) {

+    this.interruptOnFailure = interruptOnFailure;

+  }

+

+  public int getMaxFailures() {

+    return maxFailures;

+  }

+

+  public void setMaxFailures(int maxFailures) {

+    this.maxFailures = maxFailures;

+  }

+

+  public int getThreadPoolSize() {

+    return threadPoolSize;

+  }

+

+  public void setThreadPoolSize(int threadPoolSize) {

+    this.threadPoolSize = threadPoolSize;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..d8a8bd5
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/PfloNode.java
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+

+import java.io.Serializable;

+

+public class PfloNode implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private String bpmnPlfoTaskId;

+  private String label;

+

+  public PfloNode() {}

+

+  public PfloNode(String bpmnPlfoTaskId, String label) {

+    this.bpmnPlfoTaskId = bpmnPlfoTaskId;

+    this.label = label;

+  }

+

+  public static long getSerialVersionUID() {

+    return serialVersionUID;

+  }

+

+  public String getBpmnPlfoTaskId() {

+    return bpmnPlfoTaskId;

+  }

+

+  public void setBpmnPlfoTaskId(String bpmnPlfoTaskId) {

+    this.bpmnPlfoTaskId = bpmnPlfoTaskId;

+  }

+

+  public String getLabel() {

+    return label;

+  }

+

+  public void setLabel(String label) {

+    this.label = label;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..99ed995
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadNode.java
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import org.bson.types.ObjectId;

+

+public class TestHeadNode implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId testHeadId;

+  private String bpmnVthTaskId;

+

+  public TestHeadNode() {

+  }

+

+  public TestHeadNode(ObjectId testHeadId, String taskId) {

+    this.testHeadId = testHeadId;

+    this.bpmnVthTaskId = taskId;

+  }

+

+  public ObjectId getTestHeadId() {

+    return testHeadId;

+  }

+

+  public void setTestHeadId(ObjectId testHeadId) {

+    this.testHeadId = testHeadId;

+  }

+

+  public String getBpmnVthTaskId() {

+    return bpmnVthTaskId;

+  }

+

+  public void setBpmnVthTaskId(String bpmnVthTaskId) {

+    this.bpmnVthTaskId = bpmnVthTaskId;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..89c7457
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadRequest.java
@@ -0,0 +1,53 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import java.io.Serializable;

+import java.util.Map;

+

+public class TestHeadRequest implements Serializable {

+  private static final long serialVersionUID = 1L;

+  private Map<String, String> headers;

+  private Map<String, Object>  body;

+

+  public TestHeadRequest(){}

+

+  public TestHeadRequest(Map<String, String> headers,

+                         Map<String, Object> body) {

+    this.headers = headers;

+    this.body = body;

+  }

+

+  public Map<String, String> getHeaders() {

+    return headers;

+  }

+

+  public void setHeaders(Map<String, String> headers) {

+    this.headers = headers;

+  }

+

+  public Map<String, Object> getBody() {

+    return body;

+  }

+

+  public void setBody(Map<String, Object> body) {

+    this.body = body;

+  }

+

+

+

+}

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
index 0000000..55f82e9
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestHeadResult.java
@@ -0,0 +1,146 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import org.bson.types.ObjectId;

+

+import java.io.Serializable;

+import java.util.Date;

+import java.util.Map;

+

+public class TestHeadResult implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId testHeadId;

+  private String testHeadName;

+  private ObjectId testHeadGroupId;

+  private String bpmnVthTaskId;

+

+  //TODO: RG Remove maps below, setters and getters to return to normal

+  //private Map<String, String> testHeadHeaders;

+  //private int testHeadCode;

+  private int statusCode;

+

+  private TestHeadRequest testHeadRequest;

+  private Map<String, Object> testHeadResponse;

+  private Date startTime;

+  private Date endTime;

+

+  public TestHeadResult() {

+  }

+

+  public TestHeadResult(

+      ObjectId testHeadId,

+      String testHeadName,

+      ObjectId testHeadGroupId,

+      String bpmnVthTaskId,

+

+      //TODO: RG changed code to int and changed testHeadRequest from Map<String, String> to RequestContent

+      int statusCode,

+

+      TestHeadRequest testHeadRequest,

+      Map<String, Object> testHeadResponse,

+      Date startTime,

+      Date endTime) {

+    this.testHeadId = testHeadId;

+    this.testHeadName = testHeadName;

+    this.testHeadGroupId = testHeadGroupId;

+    this.bpmnVthTaskId = bpmnVthTaskId;

+

+    //this.testHeadHeaders = testHeadHeaders;

+    this.statusCode = statusCode;

+

+    this.testHeadRequest = testHeadRequest;

+    this.testHeadResponse = testHeadResponse;

+    this.startTime = startTime;

+    this.endTime = endTime;

+  }

+

+  public int getStatusCode(){return statusCode;}

+  public void setStatusCode(int testHeadCode){this.statusCode = statusCode;}

+

+  public ObjectId getTestHeadId() {

+    return testHeadId;

+  }

+

+  public void setTestHeadId(ObjectId testHeadId) {

+    this.testHeadId = testHeadId;

+  }

+

+  public String getTestHeadName() {

+    return testHeadName;

+  }

+

+  public void setTestHeadName(String testHeadName) {

+    this.testHeadName = testHeadName;

+  }

+

+  public ObjectId getTestHeadGroupId() {

+    return testHeadGroupId;

+  }

+

+  public void setTestHeadGroupId(ObjectId testHeadGroupId) {

+    this.testHeadGroupId = testHeadGroupId;

+  }

+

+  public String getBpmnVthTaskId() {

+    return bpmnVthTaskId;

+  }

+

+  public void setBpmnVthTaskId(String bpmnVthTaskId) {

+    this.bpmnVthTaskId = bpmnVthTaskId;

+  }

+

+  public TestHeadRequest getTestHeadRequest() {

+    return testHeadRequest;

+  }

+

+  public void setTestHeadRequest(TestHeadRequest testHeadRequest) {

+    this.testHeadRequest = testHeadRequest;

+  }

+

+  public Map<String, Object> getTestHeadResponse() {

+    return testHeadResponse;

+  }

+

+  public void setTestHeadResponse(Map<String, Object> testHeadResponse) {

+    this.testHeadResponse = testHeadResponse;

+  }

+

+  public Date getStartTime() {

+    return startTime;

+  }

+

+  public void setStartTime(Date startTime) {

+    this.startTime = startTime;

+  }

+

+  public Date getEndTime() {

+    return endTime;

+  }

+

+  public void setEndTime(Date endTime) {

+    this.endTime = endTime;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..b497477
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/TestInstanceCreateRequest.java
@@ -0,0 +1,215 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import com.google.common.base.Strings;

+import java.io.Serializable;

+import java.util.HashMap;

+import org.bson.types.ObjectId;

+

+public class TestInstanceCreateRequest implements Serializable {

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId testDefinitionId = null;

+  private int version = Integer.MIN_VALUE;

+  private String processDefinitionKey = null;

+

+  private String testInstanceName;

+  private String testInstanceDescription;

+  private HashMap<String, ParallelFlowInput> pfloInput;

+  private HashMap<String, Object> simulationVthInput;

+  private HashMap<String, Object> testData;

+  private HashMap<String, Object> vthInput;

+  private ObjectId createdBy;

+  private boolean useLatestTestDefinition = true;

+  private boolean simulationMode = false;

+  private long maxExecutionTimeInMillis = 0L;

+

+  public TestInstanceCreateRequest() throws Exception {

+    this.validate();

+  }

+

+  public TestInstanceCreateRequest(

+      String testInstanceName,

+      String testInstanceDescription,

+      HashMap<String, ParallelFlowInput> pfloInput,

+      HashMap<String, Object> simulationVthInput,

+      HashMap<String, Object> testData,

+      HashMap<String, Object> vthInput,

+      ObjectId createdBy,

+      boolean useLatestTestDefinition,

+      boolean simulationMode,

+      long maxExecutionTimeInMillis) throws Exception {

+    this.testInstanceName = testInstanceName;

+    this.testInstanceDescription = testInstanceDescription;

+    this.pfloInput = pfloInput;

+    this.simulationVthInput = simulationVthInput;

+    this.testData = testData;

+    this.vthInput = vthInput;

+    this.createdBy = createdBy;

+    this.useLatestTestDefinition = useLatestTestDefinition;

+    this.simulationMode = simulationMode;

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+

+    this.validate();

+  }

+

+  private void validate() throws Exception {

+    String missingFieldFormat = "The field %s is required.";

+    if (Strings.isNullOrEmpty(testInstanceName)) {

+      throw new Exception(String.format(missingFieldFormat, "testInstanceName"));

+    }

+

+    if (Strings.isNullOrEmpty(testInstanceDescription)) {

+      throw new Exception(String.format(missingFieldFormat, "testInstanceDescription"));

+    }

+

+    if (pfloInput == null) {

+      pfloInput = new HashMap<>();

+    }

+

+    if (simulationVthInput == null) {

+      simulationVthInput = new HashMap<>();

+    }

+

+    if (testData == null) {

+      testData = new HashMap<>();

+    }

+

+    if (vthInput == null) {

+      vthInput = new HashMap<>();

+    }

+

+    if (this.maxExecutionTimeInMillis < 0L) {

+      this.maxExecutionTimeInMillis = 0L;

+    }

+  }

+

+  public static long getSerialVersionUID() {

+    return serialVersionUID;

+  }

+

+  public ObjectId getTestDefinitionId() {

+    return testDefinitionId;

+  }

+

+  public void setTestDefinitionId(ObjectId testDefinitionId) {

+    this.testDefinitionId = testDefinitionId;

+  }

+

+  public int getVersion() {

+    return version;

+  }

+

+  public void setVersion(int version) {

+    this.version = version;

+  }

+

+  public String getProcessDefinitionKey() {

+    return processDefinitionKey;

+  }

+

+  public void setProcessDefinitionKey(String processDefinitionKey) {

+    this.processDefinitionKey = processDefinitionKey;

+  }

+

+  public String getTestInstanceName() {

+    return testInstanceName;

+  }

+

+  public void setTestInstanceName(String testInstanceName) {

+    this.testInstanceName = testInstanceName;

+  }

+

+  public String getTestInstanceDescription() {

+    return testInstanceDescription;

+  }

+

+  public void setTestInstanceDescription(String testInstanceDescription) {

+    this.testInstanceDescription = testInstanceDescription;

+  }

+

+  public HashMap<String, ParallelFlowInput> getPfloInput() {

+    return pfloInput;

+  }

+

+  public void setPfloInput(HashMap<String, ParallelFlowInput> pfloInput) {

+    this.pfloInput = pfloInput;

+  }

+

+  public HashMap<String, Object> getSimulationVthInput() {

+    return simulationVthInput;

+  }

+

+  public void setSimulationVthInput(HashMap<String, Object> simulationVthInput) {

+    this.simulationVthInput = simulationVthInput;

+  }

+

+  public HashMap<String, Object> getTestData() {

+    return testData;

+  }

+

+  public void setTestData(HashMap<String, Object> testData) {

+    this.testData = testData;

+  }

+

+  public HashMap<String, Object> getVthInput() {

+    return vthInput;

+  }

+

+  public void setVthInput(HashMap<String, Object> vthInput) {

+    this.vthInput = vthInput;

+  }

+

+  public ObjectId getCreatedBy() {

+    return createdBy;

+  }

+

+  public void setCreatedBy(ObjectId createdBy) {

+    this.createdBy = createdBy;

+  }

+

+  public boolean isUseLatestTestDefinition() {

+    return useLatestTestDefinition;

+  }

+

+  public void setUseLatestTestDefinition(boolean useLatestTestDefinition) {

+    this.useLatestTestDefinition = useLatestTestDefinition;

+  }

+

+  public boolean isSimulationMode() {

+    return simulationMode;

+  }

+

+  public void setSimulationMode(boolean simulationMode) {

+    this.simulationMode = simulationMode;

+  }

+

+  public long getMaxExecutionTimeInMillis() {

+    return maxExecutionTimeInMillis;

+  }

+

+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..536bc67
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/UserGroup.java
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import java.io.Serializable;

+import java.util.List;

+import org.bson.types.ObjectId;

+

+public class UserGroup implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private ObjectId groupId;

+  private List<String> permissions;

+

+  public UserGroup(){}

+  public UserGroup(ObjectId groupId, List<String> permissions) {

+    this.groupId = groupId;

+    this.permissions = permissions;

+  }

+

+  public ObjectId getGroupId() {

+    return groupId;

+  }

+

+  public void setGroupId(ObjectId groupId) {

+    this.groupId = groupId;

+  }

+

+  public List<String> getPermissions() {

+    return permissions;

+  }

+

+  public void setPermissions(List<String> permissions) {

+    this.permissions = permissions;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}

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
index 0000000..f7089a0
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/model/local/WorkflowRequest.java
@@ -0,0 +1,163 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.model.local;

+

+import org.oran.otf.common.utility.gson.Convert;

+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

+import java.io.Serializable;

+import java.util.Map;

+import org.bson.types.ObjectId;

+

+@JsonIgnoreProperties(ignoreUnknown = true)

+public class WorkflowRequest implements Serializable {

+

+  private static final long serialVersionUID = 1L;

+

+  private boolean async = false;

+  private ObjectId executorId = null;

+  private ObjectId testInstanceId = null;

+  private Map<String, ParallelFlowInput> pfloInput = null;

+  private Map<String, Object> testData = null;

+  private Map<String, Object> vthInput = null;

+  private long maxExecutionTimeInMillis = 0L;

+

+  public WorkflowRequest() throws Exception {

+    this.validate();

+  }

+

+  public WorkflowRequest(

+          boolean async,

+          ObjectId executorId,

+          ObjectId testInstanceId,

+          Map<String, ParallelFlowInput> pfloInput,

+          Map<String, Object> testData,

+          Map<String, Object> vthInput,

+          int maxExecutionTimeInMillis)

+          throws Exception {

+    this.async = async;

+    this.executorId = executorId;

+    this.testInstanceId = testInstanceId;

+    this.pfloInput = pfloInput;

+    this.testData = testData;

+    this.vthInput = vthInput;

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+

+    this.validate();

+  }

+

+  public WorkflowRequest(

+          boolean async,

+          String executorId,

+          String testInstanceId,

+          Map<String, ParallelFlowInput> pfloInput,

+          Map<String, Object> testData,

+          Map<String, Object> vthInput,

+          int maxExecutionTimeInMillis)

+          throws Exception {

+    this.async = async;

+    this.executorId = new ObjectId(executorId);

+    this.testInstanceId = new ObjectId(testInstanceId);

+    this.pfloInput = pfloInput;

+    this.testData = testData;

+    this.vthInput = vthInput;

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+

+    this.validate();

+  }

+

+  private void validate() throws Exception {

+    String missingFieldFormat = "Missing required field %s.";

+    //    if (this.async && this.asyncTopic == null) {

+    //      throw new Exception(String.format(missingFieldFormat, "asyncTopic"));

+    //    }

+

+    // Only required on the Camunda engine

+    //    if (this.executorId == null) {

+    //      throw new Exception(String.format(missingFieldFormat, "executorId"));

+    //    }

+

+    // Only required on the Camunda engine

+    //    if (this.testInstanceId == null) {

+    //      throw new Exception(String.format(missingFieldFormat, "testInstanceId"));

+    //    }

+

+    if (this.maxExecutionTimeInMillis < 0L) {

+      this.maxExecutionTimeInMillis = 0L;

+    }

+  }

+

+  public boolean isAsync() {

+    return async;

+  }

+

+  public void setAsync(boolean async) {

+    this.async = async;

+  }

+

+  public ObjectId getExecutorId() {

+    return executorId;

+  }

+

+  public void setExecutorId(ObjectId executorId) {

+    this.executorId = executorId;

+  }

+

+  public ObjectId getTestInstanceId() {

+    return testInstanceId;

+  }

+

+  public void setTestInstanceId(ObjectId testInstanceId) {

+    this.testInstanceId = testInstanceId;

+  }

+

+  public Map<String, ParallelFlowInput> getPfloInput() {

+    return pfloInput;

+  }

+

+  public void setPfloInput(Map<String, ParallelFlowInput> pfloInput) {

+    this.pfloInput = pfloInput;

+  }

+

+  public Map<String, Object> getTestData() {

+    return testData;

+  }

+

+  public void setTestData(Map<String, Object> testData) {

+    this.testData = testData;

+  }

+

+  public Map<String, Object> getVthInput() {

+    return vthInput;

+  }

+

+  public void setVthInput(Map<String, Object> vthInput) {

+    this.vthInput = vthInput;

+  }

+

+  public long getMaxExecutionTimeInMillis() {

+    return maxExecutionTimeInMillis;

+  }

+

+  public void setMaxExecutionTimeInMillis(long maxExecutionTimeInMillis) {

+    this.maxExecutionTimeInMillis = maxExecutionTimeInMillis;

+  }

+

+  @Override

+  public String toString() {

+    return Convert.objectToJson(this);

+  }

+}
\ 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
index 0000000..69d000c
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/repository/GroupRepository.java
@@ -0,0 +1,31 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.Group;

+import org.bson.types.ObjectId;

+import org.springframework.data.mongodb.repository.MongoRepository;

+import org.springframework.data.mongodb.repository.Query;

+

+import java.util.List;

+

+public interface GroupRepository extends MongoRepository<Group, String> {

+    @Query("{ 'members.userId': ?0 }")

+    public List<Group> findAllByMembersId(ObjectId membersUserId);

+    public Group findFirstByGroupName(String groupName);

+}

+

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
index 0000000..ecd2bab
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/repository/TestDefinitionRepository.java
@@ -0,0 +1,26 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.TestDefinition;

+import java.util.Optional;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public interface TestDefinitionRepository extends MongoRepository<TestDefinition, String> {

+

+  Optional<TestDefinition> findByProcessDefinitionKey(String processDefinitionKey);

+}

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
index 0000000..ee86a82
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/repository/TestExecutionRepository.java
@@ -0,0 +1,26 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.TestExecution;

+import java.util.Optional;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public interface TestExecutionRepository extends MongoRepository<TestExecution, String> {

+

+  Optional<TestExecution> findFirstByProcessInstanceId(String processInstanceId);

+}

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
index 0000000..09ab4ac
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/repository/TestHeadRepository.java
@@ -0,0 +1,28 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.TestHead;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+import java.util.Optional;

+

+public interface TestHeadRepository extends MongoRepository<TestHead, String> {

+    Optional<TestHead> findByTestHeadName(String testHeadName);

+

+

+}

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
index 0000000..16d1dcb
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/repository/TestInstanceRepository.java
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.TestInstance;

+import java.util.List;

+import java.util.Optional;

+import org.bson.types.ObjectId;

+import org.springframework.data.mongodb.repository.MongoRepository;

+import org.springframework.data.mongodb.repository.Query;

+

+public interface TestInstanceRepository extends MongoRepository<TestInstance, String> {

+

+  Optional<TestInstance> findByTestInstanceName(String testInstanceName);

+

+  @Query("{ 'testDefinitionId': ?0 }")

+  List<TestInstance> findAllByTestDefinitionId(ObjectId testDefinitionId);

+

+  @Query("{ 'testDefinitionId': ?0, 'processDefinitionId': ?1 }")

+  List<TestInstance> findAllByTestDefinitionIdAndPDId(

+      ObjectId testDefinitionId, String processDefinitionId);

+}

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
index 0000000..5dd669f
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/repository/UserRepository.java
@@ -0,0 +1,25 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.repository;

+

+import org.oran.otf.common.model.User;

+import java.util.Optional;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public interface UserRepository extends MongoRepository<User, String> {

+  Optional<User> findFirstByEmail(String email);

+}

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
index 0000000..b5e3a39
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/util/HttpUtils.java
@@ -0,0 +1,19 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.util;

+

+public class HttpUtils {}

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
index 0000000..1309d6d
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/RSAEncryptDecrypt.java
@@ -0,0 +1,54 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility;

+

+import java.security.KeyPair;

+import java.security.KeyPairGenerator;

+import java.security.NoSuchAlgorithmException;

+import javax.crypto.Cipher;

+import org.springframework.stereotype.Service;

+

+@Service

+public class RSAEncryptDecrypt {

+

+  private KeyPair keyPair;

+

+  public RSAEncryptDecrypt() throws NoSuchAlgorithmException {

+    this.keyPair = buildKeyPair();

+  }

+

+  private KeyPair buildKeyPair() throws NoSuchAlgorithmException {

+    final int keySize = 2048;

+    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");

+    keyPairGenerator.initialize(keySize);

+    return keyPairGenerator.genKeyPair();

+  }

+

+  public byte[] encrypt(String message) throws Exception {

+    Cipher cipher = Cipher.getInstance("RSA");

+    cipher.init(Cipher.ENCRYPT_MODE, this.keyPair.getPrivate());

+

+    return cipher.doFinal(message.getBytes());

+  }

+

+  public byte[] decrypt(byte[] encrypted) throws Exception {

+    Cipher cipher = Cipher.getInstance("RSA");

+    cipher.init(Cipher.DECRYPT_MODE, this.keyPair.getPublic());

+

+    return cipher.doFinal(encrypted);

+  }

+}

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
index 0000000..c781ffb
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/Utility.java
@@ -0,0 +1,84 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility;

+

+import com.fasterxml.jackson.databind.ObjectMapper;

+import com.google.common.base.Strings;

+

+import java.lang.reflect.Field;

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.Collection;

+import java.util.HashMap;

+import java.util.List;

+import java.util.Map;

+import java.util.UUID;

+

+public class Utility {

+

+  public static String getLoggerPrefix() {

+    return "[" + Thread.currentThread().getStackTrace()[2].getMethodName() + "]: ";

+  }

+

+  public static Map<?, ?> toMap(Object obj) throws Exception {

+    ObjectMapper mapper = new ObjectMapper();

+    return mapper.convertValue(obj, HashMap.class);

+  }

+

+  public static boolean isCollection(Object obj) {

+    return obj.getClass().isArray() || obj instanceof Collection;

+  }

+

+  public static List<?> toList(Object obj) {

+    if (obj == null) {

+      throw new NullPointerException("Argument cannot be null.");

+    }

+

+    List<?> list = new ArrayList<>();

+    if (obj.getClass().isArray()) {

+      list = Arrays.asList((Object[]) obj);

+    } else if (obj instanceof Collection) {

+      list = new ArrayList<>((Collection<?>) obj);

+    }

+

+    return list;

+  }

+

+  public static boolean isValidUuid(String str) {

+    if (Strings.isNullOrEmpty(str)) {

+      return false;

+    }

+    try {

+      UUID uuid = UUID.fromString(str);

+      return uuid.toString().equalsIgnoreCase(str);

+    } catch (IllegalArgumentException iae) {

+      return false;

+    }

+  }

+

+  // check a name type pair to see if it matches field in class

+  public static boolean isTypeVariablePairInClass(String variableName, Object variableValue, Class javaClass){

+    List<Field> testHeadFields = Arrays.asList(javaClass.getFields());

+    for(int i = 0; i < testHeadFields.size(); i++){

+      Field field = testHeadFields.get(i);

+      if(field.getName().equals(variableName) && field.getType().isInstance(variableValue)){

+        return true;

+      }

+    }

+    return false;

+  }

+}

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
index 0000000..5de5043
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/database/Generic.java
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.database;

+

+import java.util.Optional;

+import org.bson.types.ObjectId;

+import org.springframework.data.mongodb.repository.MongoRepository;

+

+public class Generic {

+

+  public static <T> boolean identifierExistsInCollection(

+      MongoRepository<T, String> repository, ObjectId identifier) {

+    return repository.findById(identifier.toString()).isPresent();

+  }

+

+  public static <T> T findByIdGeneric(MongoRepository<T, String> repository, ObjectId identifier) {

+    Optional<T> optionalObj = repository.findById(identifier.toString());

+    return optionalObj.orElse(null);

+  }

+

+

+}

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
index 0000000..c54359f
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/database/TestExecutionUtility.java
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.database;

+

+import org.oran.otf.common.model.TestExecution;

+import com.mongodb.client.result.UpdateResult;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.data.mongodb.core.query.Update;

+

+public class TestExecutionUtility {

+

+  public static void saveTestResult(

+      MongoTemplate mongoOperation, TestExecution execution, String testResult) {

+    Query query = new Query();

+    query.addCriteria(Criteria.where("businessKey").is(execution.getBusinessKey()));

+    Update update = new Update();

+    update.set("testResult", testResult);

+    UpdateResult result = mongoOperation.updateFirst(query, update, TestExecution.class);

+  }

+}

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
index 0000000..bc1d0af
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/gson/Convert.java
@@ -0,0 +1,95 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.gson;

+

+import com.fasterxml.jackson.core.type.TypeReference;

+import com.fasterxml.jackson.databind.DeserializationFeature;

+import com.fasterxml.jackson.databind.ObjectMapper;

+import com.google.gson.Gson;

+import com.google.gson.GsonBuilder;

+import com.google.gson.JsonDeserializationContext;

+import com.google.gson.JsonDeserializer;

+import com.google.gson.JsonElement;

+import com.google.gson.JsonParseException;

+import com.google.gson.JsonPrimitive;

+import com.google.gson.JsonSerializationContext;

+import com.google.gson.JsonSerializer;

+import com.google.gson.reflect.TypeToken;

+

+import java.io.IOException;

+import java.lang.reflect.Type;

+import java.util.HashMap;

+import java.util.Map;

+import org.bson.types.ObjectId;

+

+public class Convert {

+

+  private static final GsonBuilder gsonBuilder =

+      new GsonBuilder()

+          .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")

+          .registerTypeAdapter(

+              ObjectId.class,

+              new JsonSerializer<ObjectId>() {

+                @Override

+                public JsonElement serialize(

+                    ObjectId src, Type typeOfSrc, JsonSerializationContext context) {

+                  return new JsonPrimitive(src.toHexString());

+                }

+              })

+          .registerTypeAdapter(

+              ObjectId.class,

+              new JsonDeserializer<ObjectId>() {

+                @Override

+                public ObjectId deserialize(

+                    JsonElement json, Type typeOfT, JsonDeserializationContext context)

+                    throws JsonParseException {

+                  return new ObjectId(json.getAsString());

+                }

+              });

+

+  public static Gson getGson() {

+    return gsonBuilder.create();

+  }

+

+  public static String mapToJson(Map map) {

+    if (map.isEmpty()) {

+      return "{}";

+    }

+    return getGson().toJson(map);

+  }

+

+  public static Map<String, Object> jsonToMap(String json) {

+    Type type = new TypeToken<HashMap<String, Object>>() {

+    }.getType();

+    return getGson().fromJson(json, type);

+  }

+

+  public static String objectToJson(Object obj) {

+    return getGson().toJson(obj);

+  }

+

+  public static<T> T mapToObject(Map map, TypeReference<T> typeReference) throws IOException {

+    return jsonToObject(mapToJson(map), typeReference);

+  }

+

+  public static <T> T jsonToObject(String json, TypeReference<T> typeReference) throws IOException {

+    ObjectMapper objectMapper = new ObjectMapper();

+    objectMapper

+        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);

+    return objectMapper.readValue(json, typeReference);

+  }

+}

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
index 0000000..1b224fc
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/gson/GsonUtils.java
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.gson;

+

+import com.google.gson.Gson;

+import com.google.gson.GsonBuilder;

+import com.google.gson.JsonDeserializationContext;

+import com.google.gson.JsonDeserializer;

+import com.google.gson.JsonElement;

+import com.google.gson.JsonParseException;

+import com.google.gson.JsonPrimitive;

+import com.google.gson.JsonSerializationContext;

+import com.google.gson.JsonSerializer;

+import com.google.gson.reflect.TypeToken;

+import java.lang.reflect.Type;

+import java.util.HashMap;

+import java.util.Map;

+import org.bson.types.ObjectId;

+

+public class GsonUtils {

+    private static final GsonBuilder gsonBuilder =

+            new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")

+                    .registerTypeAdapter(ObjectId.class, new JsonSerializer<ObjectId>() {

+                        @Override

+                        public JsonElement serialize(ObjectId src, Type typeOfSrc,

+                                                     JsonSerializationContext context) {

+                            return new JsonPrimitive(src.toHexString());

+                        }

+                    }).registerTypeAdapter(ObjectId.class, new JsonDeserializer<ObjectId>() {

+                @Override

+                public ObjectId deserialize(JsonElement json, Type typeOfT,

+                                            JsonDeserializationContext context) throws JsonParseException {

+                    return new ObjectId(json.getAsString());

+                }

+            });

+

+    public static Gson getGson() {

+        return gsonBuilder.create();

+    }

+

+    private static final Gson gson = getGson();

+    private static final Type TT_mapStringString = new TypeToken<Map<String,String>>(){}.getType();

+

+    public static Map<String, String> jsonToMapStringString(String json) {

+        Map<String, String> ret = new HashMap<String, String>();

+        if (json == null || json.isEmpty())

+            return ret;

+        return gson.fromJson(json, TT_mapStringString);

+    }

+    public static String mapStringObjectToJson(Map<String, Object> map) {

+        if (map == null)

+            map = new HashMap<String, Object>();

+        return gson.toJson(map);

+    }

+}
\ 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
index 0000000..2af3f90
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/http/RequestUtility.java
@@ -0,0 +1,160 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.http;

+

+import com.google.common.base.Strings;

+import java.io.IOException;

+import java.io.UnsupportedEncodingException;

+import java.util.HashMap;

+import java.util.Map;

+import java.util.Timer;

+import java.util.TimerTask;

+import java.util.concurrent.ExecutionException;

+import java.util.concurrent.Future;

+import org.apache.http.HttpResponse;

+import org.apache.http.client.methods.HttpGet;

+import org.apache.http.client.methods.HttpPost;

+import org.apache.http.client.methods.HttpRequestBase;

+import org.apache.http.entity.StringEntity;

+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;

+import org.apache.http.impl.nio.client.HttpAsyncClients;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+

+public class RequestUtility {

+

+  private static Logger logger = LoggerFactory.getLogger(RequestUtility.class);

+

+  public static void postAsync(String url, String body, Map<String, String> headers)

+      throws IOException, InterruptedException, ExecutionException {

+    HttpPost post = buildPost(url, body, headers);

+    executeAsync(post);

+  }

+

+  public static HttpResponse postSync(String url, String body, Map<String, String> headers)

+      throws IOException, InterruptedException, ExecutionException {

+    HttpPost post = buildPost(url, body, headers);

+    return executeSync(post);

+  }

+

+  public static HttpResponse postSync(

+      String url, String body, Map<String, String> headers, int timeoutInMillis)

+      throws IOException, InterruptedException, ExecutionException {

+    HttpPost post = buildPost(url, body, headers);

+    return executeSync(post, timeoutInMillis);

+  }

+

+  public static HttpResponse getSync(String url, Map<String, String> headers)

+      throws IOException, InterruptedException, ExecutionException {

+    HttpGet get = buildGet(url, headers);

+    return executeSync(get);

+  }

+

+  public static HttpResponse getSync(String url, Map<String, String> headers, int timeoutInMillis)

+      throws IOException, InterruptedException, ExecutionException {

+    if (timeoutInMillis < 0) {

+      throw new IllegalArgumentException("The timeoutInMillis must be a value greater than 0.");

+    }

+

+    HttpGet get = buildGet(url, headers);

+    return executeSync(get, timeoutInMillis);

+  }

+

+  public static void getAsync(String url, Map<String, String> headers) throws IOException {

+    HttpGet get = buildGet(url, headers);

+    executeAsync(get);

+  }

+

+  private static HttpPost buildPost(String url, String body, Map<String, String> headers)

+      throws UnsupportedEncodingException {

+    if (Strings.isNullOrEmpty(url) || Strings.isNullOrEmpty(body)) {

+      return null;

+    } else if (headers == null) {

+      headers = new HashMap<>();

+    }

+

+    HttpPost post = new HttpPost(url);

+    headers.forEach(post::setHeader);

+    post.setEntity(new StringEntity(body));

+    return post;

+  }

+

+  private static HttpGet buildGet(String url, Map<String, String> headers) {

+    if (Strings.isNullOrEmpty(url)) {

+      return null;

+    } else if (headers == null) {

+      headers = new HashMap<>();

+    }

+

+    HttpGet get = new HttpGet(url);

+    headers.forEach(get::setHeader);

+    return get;

+  }

+

+  private static HttpResponse executeSync(HttpRequestBase request)

+      throws IOException, InterruptedException, ExecutionException {

+    CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();

+    try {

+      httpClient.start();

+      Future<HttpResponse> future = httpClient.execute(request, null);

+      return future.get();

+    } finally {

+      httpClient.close();

+    }

+  }

+

+  private static HttpResponse executeSync(HttpRequestBase request, int timeoutInMillis)

+      throws IOException, InterruptedException, ExecutionException {

+    if (timeoutInMillis < 0) {

+      throw new IllegalArgumentException("The timeoutInMillis must be a value greater than 0.");

+    }

+

+    // Create a timer task that will abort the task (the request) after the specified time. This

+    // task will run *timeoutInMillis* ms

+    TimerTask task =

+        new TimerTask() {

+          @Override

+          public void run() {

+            if (request != null) {

+              request.abort();

+            }

+          }

+        };

+

+    CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();

+    try {

+      httpClient.start();

+      // Start the timer before making the request.

+      new Timer(true).schedule(task, timeoutInMillis);

+      Future<HttpResponse> future = httpClient.execute(request, null);

+      return future.get();

+    } finally {

+      httpClient.close();

+    }

+  }

+

+  private static void executeAsync(HttpRequestBase request) throws IOException {

+    CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();

+    try {

+      httpClient.start();

+      httpClient.execute(request, null);

+      logger.debug("Sent asynchronous request.");

+    } finally {

+      httpClient.close();

+    }

+  }

+}

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
index 0000000..919897c
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/http/ResponseUtility.java
@@ -0,0 +1,107 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.http;

+

+import org.oran.otf.common.model.local.OTFApiResponse;

+import javax.ws.rs.core.MediaType;

+import javax.ws.rs.core.Response;

+

+public class ResponseUtility {

+

+  public static class Build {

+

+    public static Response okRequest() {

+      return Response.ok().build();

+    }

+

+    public static Response badRequest() {

+      return Response.status(400).build();

+    }

+

+    public static Response okRequestWithMessage(String msg) {

+      return Response.status(200)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(200, msg))

+          .build();

+    }

+

+    public static Response okRequestWithObject(Object obj) {

+      return Response.status(200).type(MediaType.APPLICATION_JSON).entity(obj).build();

+    }

+

+    public static Response badRequestWithMessage(String msg) {

+      return Response.status(400)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(400, msg))

+          .build();

+    }

+

+    public static Response internalServerError() {

+      return Response.status(500).build();

+    }

+

+    public static Response internalServerErrorWithMessage(String msg) {

+      return Response.status(500)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(500, msg))

+          .build();

+    }

+

+    public static Response unauthorized() {

+      return Response.status(401).build();

+    }

+    public static Response unauthorizedWithMessage(String msg) {

+      return Response.status(401)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(401, msg))

+          .build();

+    }

+

+    public static Response notFoundWithMessage(String msg) {

+      return Response.status(404)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(404, msg))

+          .build();

+    }

+

+    public static Response serviceUnavailableWithMessage(String msg) {

+      return Response.status(503)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(503, msg))

+          .build();

+    }

+

+    public static Response serviceUnavailable() {

+      return Response.status(503).build();

+    }

+

+    public static Response genericWithCode(int code) {

+      return Response.status(code).build();

+    }

+

+    public static Response genericWithMessage(int code, String msg) {

+      return Response.status(code)

+          .type(MediaType.APPLICATION_JSON)

+          .entity(new OTFApiResponse(code, msg))

+          .build();

+    }

+

+    public static Response genericWithObject(int code, Object obj) {

+      return Response.status(code).type(MediaType.APPLICATION_JSON).entity(obj).build();

+    }

+  }

+}

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
index 0000000..8327a81
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/logger/ErrorCode.java
@@ -0,0 +1,36 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.logger;

+

+public enum ErrorCode {

+  PermissionError(100),

+  AvailabilityError(200),

+  DataError(300),

+  SchemaError(400),

+  BusinessProcesssError(500),

+  UnknownError(900);

+

+  private int value;

+

+  ErrorCode(int value) {

+    this.value = value;

+  }

+

+  public int getValue() {

+    return this.value;

+  }

+}

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
index 0000000..10c45d8
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/logger/LoggerStartupListener.java
@@ -0,0 +1,91 @@
+/*-

+ * ============LICENSE_START=======================================================

+ * ONAP - SO

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.

+ * ================================================================================

+ * Modifications Copyright (c) 2019 Samsung

+ * ================================================================================

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ * ============LICENSE_END=========================================================

+ */

+

+package org.oran.otf.common.utility.logger;

+

+import ch.qos.logback.classic.Level;

+import ch.qos.logback.classic.LoggerContext;

+import ch.qos.logback.classic.spi.LoggerContextListener;

+import ch.qos.logback.core.Context;

+import ch.qos.logback.core.spi.ContextAwareBase;

+import ch.qos.logback.core.spi.LifeCycle;

+import java.net.InetAddress;

+import java.net.UnknownHostException;

+import org.slf4j.Logger;

+import org.slf4j.LoggerFactory;

+import org.springframework.stereotype.Component;

+

+@Component

+public class LoggerStartupListener extends ContextAwareBase

+    implements LoggerContextListener, LifeCycle {

+

+  private static final Logger logger = LoggerFactory.getLogger(LoggerStartupListener.class);

+  private boolean started = false;

+

+  @Override

+  public void start() {

+    if (started) {

+      return;

+    }

+    InetAddress addr = null;

+    try {

+      addr = InetAddress.getLocalHost();

+    } catch (UnknownHostException e) {

+      logger.error("UnknownHostException", e);

+    }

+    Context context = getContext();

+    if (addr != null) {

+      context.putProperty("server.name", addr.getHostName());

+    }

+    started = true;

+  }

+

+  @Override

+  public void stop() {

+  }

+

+  @Override

+  public boolean isStarted() {

+    return started;

+  }

+

+  @Override

+  public boolean isResetResistant() {

+    return true;

+  }

+

+  @Override

+  public void onReset(LoggerContext arg0) {

+  }

+

+  @Override

+  public void onStart(LoggerContext arg0) {

+  }

+

+  @Override

+  public void onStop(LoggerContext arg0) {

+  }

+

+  @Override

+  public void onLevelChange(ch.qos.logback.classic.Logger logger, Level level) {

+  }

+}

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
index 0000000..1103c53
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/logger/MessageEnum.java
@@ -0,0 +1,35 @@
+/*-

+ * ============LICENSE_START=======================================================

+ * ONAP - SO

+ * ================================================================================

+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.

+ * ================================================================================

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ * ============LICENSE_END=========================================================

+ */

+

+package org.oran.otf.common.utility.logger;

+

+

+public enum MessageEnum {

+  // Api Handler Messages

+  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,

+  // Resource Adapter Messages

+  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,

+  // BPEL engine Messages

+  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,

+  // ASDC Messages

+  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,

+  // Default Messages, in case Log catalog is not defined

+  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

+}

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
index 0000000..e1749bb
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/PermissionChecker.java
@@ -0,0 +1,57 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.permissions;

+

+import org.oran.otf.common.model.Group;

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.repository.GroupRepository;

+

+import java.util.Collection;

+

+public class PermissionChecker {

+    //check is a user have a certain permission in a group

+    public static boolean hasPermissionTo(User user,Group group,String permission, GroupRepository groupRepository){

+        UserPermission userPermission = new PermissionUtil().buildUserPermission(user,groupRepository);

+        return hasPermissionTo(userPermission,group,permission);

+    }

+    public static boolean hasPermissionTo(User user, Group group, Collection<String> permissions, GroupRepository groupRepository){

+        UserPermission userPermission = new PermissionUtil().buildUserPermission(user,groupRepository);

+        for(String permission : permissions){

+            if(!hasPermissionTo(userPermission,group,permission)){

+                return false;

+            }

+        }

+        return true;

+    }

+    // check a users list of permission in a group

+    private static boolean hasPermissionTo(UserPermission userPermission, Group group,String permission){

+        switch (permission.toUpperCase()) {

+            case (UserPermission.Permission.READ):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.READ);

+            case (UserPermission.Permission.WRITE):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.WRITE);

+            case (UserPermission.Permission.EXECUTE):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.EXECUTE);

+            case (UserPermission.Permission.DELETE):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.DELETE);

+            case (UserPermission.Permission.MANAGEMENT):

+                return userPermission.hasAccessTo(group.get_id().toString(),UserPermission.Permission.MANAGEMENT);

+            default:

+                return false;// reaches here when permission provided is not an option

+        }

+    }

+}
\ 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
index 0000000..e8cdfea
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/PermissionUtil.java
@@ -0,0 +1,237 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.permissions;

+

+import org.oran.otf.common.model.Group;

+import org.oran.otf.common.model.GroupMember;

+import org.oran.otf.common.model.Role;

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.repository.GroupRepository;

+

+import java.util.*;

+

+public class PermissionUtil {

+    //build userPermission object which contains all access control information of the user

+    public UserPermission buildUserPermission(User user, GroupRepository groupRepository) {

+        UserPermission userPermission = new UserPermission();

+        userPermission.setUser(user);

+        Map<String,Set<String>> userAccessMap; // map from group to permission that user have in that group

+

+        userAccessMap = mapGroupsToPermission(user,groupRepository);

+        userPermission.setUserAccessMap(userAccessMap);

+        return userPermission;

+    }

+    // return if user have specified permission in a certain group

+    // ***********only use this groups that the user is in directly (non-child and non parents)****************

+    public static boolean hasPermissionTo (String permission,User user, Group group) {

+        Set<String> possiblePermissions= getUserGroupPermissions(user,group);

+        return possiblePermissions.stream().anyMatch(p-> p.equalsIgnoreCase(permission)); //

+    }

+    // Get all the permissions the user have in a certain group

+    public static Set<String> getUserGroupPermissions(User user, Group group){

+        Set<String> permissionsAllowed = new HashSet<>();

+        Set<String> usersAssignedRoles = findUserRoles(user,group);

+        if(usersAssignedRoles.isEmpty()) // empty set permissions because the user have no roles in the group aka not a member

+            return permissionsAllowed;

+        //get every single permissions for each role that the user have.

+        for(String role : usersAssignedRoles){

+             permissionsAllowed.addAll(getRolePermissions(role,group));

+        }

+        return permissionsAllowed;

+    }

+    //get the permissions associated with the userRoleName in group

+    public static Set<String> getRolePermissions(String userRoleName,Group group)

+    {

+        for(Role role : group.getRoles())

+        {

+            if(role.getRoleName().equalsIgnoreCase(userRoleName))

+            {

+                return new HashSet<String>(role.getPermissions());

+            }

+        }

+        return new HashSet<String>(); // empty string set if the role name cant be found in the group

+    }

+    // find the user's role in the specified group

+    public static Set<String> findUserRoles(User user,Group group){

+        for(GroupMember member : group.getMembers())

+        {

+            // if userId matches then get all the user's role in the group

+            if(member.getUserId().toString().equals(user.get_id().toString()))

+                return new HashSet<String>(member.getRoles());

+        }

+        return new HashSet<String>(); //if user have no roles

+    }

+    // create map that where key is the group id and value = users permission (string) that that group

+    private Map<String,Set<String>> mapGroupsToPermission(User user, GroupRepository groupRepository){

+        Map<String,Set<String>> groupAccessMap = new HashMap<>();

+        List<Group> enrolledGroups = groupRepository.findAllByMembersId(user.get_id());// enrolledGroups = groups that user is a member of

+        Map<String,Group> allGroupMap = groupListToMap(groupRepository.findAll());

+        // get all permission in the groups the user is ia member of

+        for(Group group: enrolledGroups) {

+            Set<String> permissions = getUserGroupPermissions(user,group);

+            groupAccessMap.put(group.get_id().toString(),convertPermissions(permissions));

+        }

+        //assign add read to all parent groups

+        Set<String> parentGroupsId = getParentGroups(enrolledGroups,allGroupMap);

+        for(String parentId : parentGroupsId)

+        {

+            // if parent access role already exist in

+            // group access map cause they are a member

+            if(groupAccessMap.get(parentId)!= null)

+                groupAccessMap.get(parentId).add(UserPermission.Permission.READ);

+            else

+                groupAccessMap.put(parentId,new HashSet<String>(Arrays.asList(UserPermission.Permission.READ)));

+        }

+        // if there is management role

+        // then assign read access to children

+        if(hasManagementRole(user,enrolledGroups)){

+//            Set<String>childIds = getChildrenGroupsId(enrolledGroups,allGroupMap,user);

+            for(Group enrolledGroup : enrolledGroups) {

+                // if enrolled groups is a management group

+                if(hasPermissionTo(UserPermission.Permission.MANAGEMENT,user,enrolledGroup)){

+                    // if there is management role then get all the child of that group, do this for all management groups

+                    Set<String> childIds= getChildrenGroupsId(Arrays.asList(enrolledGroup),allGroupMap,user);

+                    Set<String> userGroupPermissions = convertPermissions(getUserGroupPermissions(user,enrolledGroup));

+                    for(String childId : childIds){

+                        if (groupAccessMap.get(childId) != null)

+                            groupAccessMap.get(childId).addAll(userGroupPermissions);

+                        else{

+                            groupAccessMap.put(childId,userGroupPermissions);

+                        }

+                    }

+                }

+            }

+        }

+        return groupAccessMap;

+    }

+    // check is user have managementRole

+    private boolean hasManagementRole(User user, List<Group> enrolledGroups)

+    {

+        for(Group group: enrolledGroups){

+            if(hasPermissionTo(UserPermission.Permission.MANAGEMENT,user,group))

+            {

+                return true;

+            }

+        }

+        return false;

+    }

+    // get the parent groups starting from the enrolled group of the user

+    private Set<String> getParentGroups(List<Group> enrolledGroup,Map<String,Group> groupMap )

+    {

+        Set<String> parentGroups = new HashSet<>();

+        return lookUp(enrolledGroup,groupMap,parentGroups);

+    }

+    //recursive lookup starting at the enrolled groups that the user is a member of

+    private Set<String> lookUp(List<Group> groupsToCheck, Map<String,Group> groupMap,Set<String> resultSet)

+    {

+        //base case: nothing to check anymore

+        if(groupsToCheck.isEmpty())

+            return resultSet;

+        //This is the parents directly above the current groups that are being checked

+        List<Group> currentParentGroups = new ArrayList<>();

+

+        for(Group group : groupsToCheck)

+        {

+            if(group.getParentGroupId() != null) // if there is a parent

+            {

+                String parentId = group.getParentGroupId().toString();

+                Group parentGroup = groupMap.get(parentId);

+                resultSet.add(parentId);

+                currentParentGroups.add(parentGroup); // add to currentParentGroup so it can be used recursively check for more parents

+            }

+        }

+        return lookUp(currentParentGroups,groupMap,resultSet);

+    }

+    // convert a list of groups to a map of group ids to group

+    private Map<String,Group> groupListToMap(List<Group> allGroups)

+    {

+        Map<String,Group> groupMap = new HashMap<>();

+        allGroups.forEach(group -> groupMap.put(group.get_id().toString(),group));

+        return groupMap;

+    }

+    //get all the child group

+    private Set<String> getChildrenGroupsId(List<Group> enrolledGroup, Map<String,Group> allGroupsMap, User user)

+    {

+        Set<String> childrenGroups = new HashSet<>();

+        Set<String> managementGroupIds = getManagementGroupIds(enrolledGroup,user);

+        return  lookForChildren(managementGroupIds,allGroupsMap,childrenGroups);

+    }

+

+    private Set<String> getManagementGroupIds(List<Group> enrolledGroups,User user)

+    {

+        Set<String> parentIds = new HashSet<>();

+        for(Group group: enrolledGroups)

+        {

+            if(hasPermissionTo(UserPermission.Permission.MANAGEMENT,user,group)) // has Management permission

+            {

+                parentIds.add(group.get_id().toString());

+            }

+        }

+        return parentIds;

+    }

+    //recursive look down for childrens via breath first search

+    private Set<String> lookForChildren (Set<String> parentIds, Map<String,Group> allGroupsMap, Set<String> resultSet)

+    {

+        //base case = no groups to check anymore;

+        if (parentIds.isEmpty())

+            return resultSet;

+

+        Set<String> currentChildrenIds = new HashSet<>();

+        for(String groupId : allGroupsMap.keySet())

+        {

+            Group possibleChildGroup = allGroupsMap.get(groupId);

+            if(isChildOf(parentIds,possibleChildGroup)) // if parent id is the same

+            {

+                currentChildrenIds.add(groupId);

+                resultSet.add(groupId);

+            }

+        }

+        return lookForChildren(currentChildrenIds,allGroupsMap,resultSet);

+    }

+    //check if a group is a child of a list of parent group ids

+    private boolean isChildOf(Set<String>parentGroupIds, Group childGroup){

+        for(String parentId: parentGroupIds)

+        {

+            if(isChildOf(parentId,childGroup))

+                return true;

+        }

+        return false;

+    }

+    //check is group has parent that is specified by parentId

+    private boolean isChildOf(String parentId,Group childGroup) {

+        if(childGroup.getParentGroupId() == null)

+            return false;

+       return childGroup.getParentGroupId().toString().equals(parentId);

+    }

+

+    private Set<String> convertPermissions (Set<String> permissions){

+        Set<String> result = new HashSet<>();

+        for (String permission: permissions){

+            if(permission.equalsIgnoreCase(UserPermission.Permission.READ))

+                result.add(UserPermission.Permission.READ);

+            else if (permission.equalsIgnoreCase(UserPermission.Permission.WRITE))

+                result.add(UserPermission.Permission.WRITE);

+            else if (permission.equalsIgnoreCase(UserPermission.Permission.DELETE))

+                result.add(UserPermission.Permission.DELETE);

+            else if (permission.equalsIgnoreCase(UserPermission.Permission.EXECUTE))

+                result.add(UserPermission.Permission.EXECUTE);

+            else if (permission.equalsIgnoreCase(UserPermission.Permission.MANAGEMENT))

+                result.add(UserPermission.Permission.MANAGEMENT);

+        }

+            return result;

+    }

+}

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
index 0000000..1883721
--- /dev/null
+++ b/otf-service-api/src/main/java/org/oran/otf/common/utility/permissions/UserPermission.java
@@ -0,0 +1,58 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.common.utility.permissions;

+

+import org.oran.otf.common.model.User;

+

+import java.util.Map;

+import java.util.Set;

+

+public class UserPermission {

+    private User user;

+    private Map<String,Set<String>> userAccessMap;

+

+    public User getUser() {

+        return user;

+    }

+

+    public void setUser(User user) {

+        this.user = user;

+    }

+

+    public Map<String, Set<String>> getUserAccessMap() {

+        return userAccessMap;

+    }

+

+    public void setUserAccessMap(Map<String,Set<String>> userAccessMap) {

+        this.userAccessMap = userAccessMap;

+    }

+

+    public boolean  hasAccessTo(String groupId,String permission) {

+        if (userAccessMap.get(groupId) == null) {

+            return false;

+        }

+        Set<String> group = userAccessMap.get(groupId);

+        return group.stream().anyMatch(groupPermission->groupPermission.equalsIgnoreCase(permission));

+    }

+    public class Permission{

+        public static final String READ = "READ";

+        public static final String WRITE = "WRITE";

+        public static final String EXECUTE = "EXECUTE";

+        public static final String DELETE = "DELETE";

+        public static final String MANAGEMENT ="MANAGEMENT";

+    }

+}

diff --git a/otf-service-api/src/main/resources/application.properties b/otf-service-api/src/main/resources/application.properties
new file mode 100644
index 0000000..0a68a60
--- /dev/null
+++ b/otf-service-api/src/main/resources/application.properties
@@ -0,0 +1,50 @@
+# Tomcat

+server.port=8443

+server.port.http=8080

+security.require-ssl=false

+

+server.ssl.key-store-type=PKCS12

+server.ssl.key-store=${OTF_CERT_PATH}

+server.ssl.key-store-password=${OTF_CERT_PASS}

+#server.servlet.context-path=/otf/api

+#spring.jersey.application-path=/otf

+#springfox.documentation.swagger.v2.path=/otf/api/swagger.json

+

+# MongoDB

+otf.mongo.hosts=${OTF_MONGO_HOSTS}

+otf.mongo.username=${OTF_MONGO_USERNAME}

+otf.mongo.password=${OTF_MONGO_PASSWORD}

+otf.mongo.replicaSet=${OTF_MONGO_REPLICASET}

+otf.mongo.database=${OTF_MONGO_DATABASE}

+

+# Jackson

+spring.jackson.default-property-inclusion=always

+

+# Logging

+logging.level.org.springframework.web=DEBUG

+logging.level.org.hibernate=ERROR

+logging.file.max-history=5

+logging.file=otf/logs/serviceapi.log

+logging.path=otf/logs

+

+spring.resources.add-mappings=true

+

+ssl.flag =${https-only.flag:true}

+#springfox.documentation.auto-startup=false

+#springfox.documentation.swagger.v2.path=/otf/swagger.json

+

+#config

+aaf.enabled=true

+aaf.call-timeout=10000

+aaf.conn-timeout=6000

+aaf.default-realm=localhost

+aaf.env=PROD

+aaf.locate-url=https://localhost

+aaf.lur-class=org.onap.aaf.cadi.aaf.v2_0.AAFLurPerm

+aaf.url=https://localhost

+basic-realm=localhost

+basic-warn=true

+cadi-latitude=38.62782

+cadi-longitude=-90.19458

+cadi-protocols=TLSv1.1,TLSv1.2

+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
index 0000000..544bdea
--- /dev/null
+++ b/otf-service-api/src/main/resources/banner.txt
@@ -0,0 +1,8 @@
+                                                           U  ___ u   _____     _____

+                                                            \/"_ \/  |_ " _|   |" ___|

+                                                            | | | |    | |    U| |_  u

+                                                        .-,_| |_| |   /| |\   \|  _|/

+                                                         \_)-\___/   u |_|U    |_|

+                                                              \\     _// \\_   )(\\,-

+                                                             (__)   (__) (__) (__)(_/

+

diff --git a/otf-service-api/src/main/resources/truststore2018.jks b/otf-service-api/src/main/resources/truststore2018.jks
new file mode 100644
index 0000000..5d52914
--- /dev/null
+++ b/otf-service-api/src/main/resources/truststore2018.jks
Binary files 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
index 0000000..f6d9f66
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/config/DataConfig2.java
@@ -0,0 +1,109 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.config;

+

+import com.mongodb.MongoClient;

+import com.mongodb.MongoClientOptions;

+import com.mongodb.MongoCredential;

+import com.mongodb.ServerAddress;

+import de.flapdoodle.embed.mongo.Command;

+import de.flapdoodle.embed.mongo.MongodExecutable;

+import de.flapdoodle.embed.mongo.MongodStarter;

+import de.flapdoodle.embed.mongo.config.DownloadConfigBuilder;

+import de.flapdoodle.embed.mongo.config.ExtractedArtifactStoreBuilder;

+import de.flapdoodle.embed.mongo.config.IMongodConfig;

+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;

+import de.flapdoodle.embed.mongo.config.Net;

+import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;

+import de.flapdoodle.embed.mongo.distribution.Version;

+import de.flapdoodle.embed.process.config.IRuntimeConfig;

+import de.flapdoodle.embed.process.config.store.HttpProxyFactory;

+import de.flapdoodle.embed.process.runtime.Network;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.autoconfigure.AutoConfigureBefore;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.context.annotation.Profile;

+import org.springframework.data.mongodb.config.AbstractMongoConfiguration;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

+

+

+@Configuration

+@EnableMongoRepositories(basePackages = "org.oran.otf.common.repository")

+@Profile("test")

+public class DataConfig2 extends AbstractMongoConfiguration {

+

+  @Value("${otf.embedded.host}")

+  private String host;

+

+  @Value("${otf.embedded.port}")

+  private int port;

+

+

+  @Value("${otf.embedded.database}")

+  private String database;

+

+  public DataConfig2(){

+  }

+

+  @Override

+  protected String getDatabaseName() {

+    return database;

+  }

+

+  /*

+  @Override

+  public MongoClient mongoClient() {

+    MongoCredential credential = MongoCredential.createScramSha1Credential(username, database, password.toCharArray());

+

+    MongoClientOptions options = MongoClientOptions

+        .builder()

+        .sslEnabled(false)

+        .requiredReplicaSetName(replicaSet)

+        .build();

+

+    String[] hostArray = hosts.split(",");

+    ArrayList<ServerAddress> hosts = new ArrayList<>();

+

+    for (String host : hostArray) {

+      String[] hostSplit = host.split(":");

+      hosts.add(new ServerAddress(hostSplit[0], Integer.parseInt(hostSplit[1])));

+    }

+

+    return new MongoClient(hosts, credential, options);

+  }

+

+  @Override

+  public @Bean

+  MongoTemplate mongoTemplate() {

+    return new MongoTemplate(mongoClient(), database);

+  }

+*/

+

+  @Override

+  public MongoClient mongoClient(){

+    return new MongoClient();

+  }

+

+  @Override

+  public @Bean MongoTemplate mongoTemplate(){

+    return new MongoTemplate(new MongoClient(host, port), "test");

+  }

+

+}

+

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
index 0000000..a5243a5
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/config/InMemory.java
@@ -0,0 +1,69 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.config;

+

+import de.flapdoodle.embed.mongo.Command;

+import de.flapdoodle.embed.mongo.MongodExecutable;

+import de.flapdoodle.embed.mongo.MongodStarter;

+import de.flapdoodle.embed.mongo.config.DownloadConfigBuilder;

+import de.flapdoodle.embed.mongo.config.ExtractedArtifactStoreBuilder;

+import de.flapdoodle.embed.mongo.config.IMongodConfig;

+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;

+import de.flapdoodle.embed.mongo.config.Net;

+import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;

+import de.flapdoodle.embed.mongo.distribution.Version.Main;

+import de.flapdoodle.embed.process.config.IRuntimeConfig;

+import de.flapdoodle.embed.process.config.store.HttpProxyFactory;

+import de.flapdoodle.embed.process.runtime.Network;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.context.annotation.Bean;

+import org.springframework.context.annotation.Configuration;

+import org.springframework.context.annotation.Profile;

+

+@Configuration

+@Profile("test")

+public class InMemory {

+  @Autowired MongodStarter mongodStarter;

+

+  @Bean

+  public MongodStarter mongodStarter(){

+    Command command = Command.MongoD;

+    IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()

+        .defaults(command)

+        .artifactStore(new ExtractedArtifactStoreBuilder()

+            .defaults(command)

+            .download(new DownloadConfigBuilder()

+                .defaultsForCommand(command)

+                //.downloadPath("http://fastdl.mongodb.org/win32/")

+                .proxyFactory(new HttpProxyFactory("localhost",8080))))

+             .build();

+

+    MongodStarter starter = MongodStarter.getInstance(runtimeConfig);

+

+    return MongodStarter.getInstance(runtimeConfig);

+  }

+  @Bean

+  public MongodExecutable mongodExecutable()throws Exception{

+    IMongodConfig mongodConfig = new MongodConfigBuilder().version(Main.PRODUCTION)

+        .net(new Net("localhost", 5555, Network.localhostIsIPv6()))

+        .build();

+    //MongodStarter starter = MongodStarter.getDefaultInstance();

+    return mongodStarter.prepare(mongodConfig);

+

+  }

+

+}

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
index 0000000..85d7016
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/ExecutionServiceRouteIT.java
@@ -0,0 +1,79 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.integration.services;

+

+import org.oran.otf.api.Application;

+import org.oran.otf.api.tests.shared.MemoryDatabase;

+import io.restassured.RestAssured;

+import org.junit.After;

+import org.junit.AfterClass;

+import org.junit.Before;

+import org.junit.BeforeClass;

+import org.junit.Ignore;

+import org.junit.Test;

+import org.junit.runner.RunWith;

+import org.springframework.boot.test.context.SpringBootTest;

+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

+import org.springframework.boot.web.server.LocalServerPort;

+import org.springframework.test.context.ActiveProfiles;

+import org.springframework.test.context.TestPropertySource;

+import org.springframework.test.context.junit4.SpringRunner;

+

+@RunWith(SpringRunner.class)

+@SpringBootTest(

+    webEnvironment = WebEnvironment.RANDOM_PORT,

+    classes = {Application.class}

+)

+@TestPropertySource("classpath:application-test.properties")

+@ActiveProfiles("test")

+public class ExecutionServiceRouteIT {

+  @LocalServerPort

+  private int port;

+

+  @BeforeClass

+  public static void setup() throws Exception{

+    MemoryDatabase.setup();

+  }

+  @AfterClass

+  public static void cleanup(){

+    MemoryDatabase.cleanup();

+  }

+

+  @Before

+  public void setupRestAssured() throws Exception{

+    RestAssured.port = port;

+    RestAssured.urlEncodingEnabled = false;

+    RestAssured.baseURI = "https://localhost";

+    RestAssured.basePath="/otf/api/testExecution/v1";

+    RestAssured.useRelaxedHTTPSValidation();

+  }

+

+  @Ignore

+  @Test

+  public void testExecutionServiceRouteRespondsWith200(){}

+  @Test

+  public void testExecutionServiceRouteStatusRespondsWithOnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/status/executionId/abced").then().assertThat().statusCode(401);

+  }

+  @Test

+  public void testExecutionServiceRouteExecutionIdRespondsWithOnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/executionId/abced").then().assertThat().statusCode(401);

+  }

+

+

+}

+

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
index 0000000..a04169e
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/HealthRouteIT.java
@@ -0,0 +1,80 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.integration.services;

+

+import static org.hamcrest.CoreMatchers.equalTo;

+

+import org.oran.otf.api.Application;

+import org.oran.otf.api.tests.shared.MemoryDatabase;

+import io.restassured.RestAssured;

+import org.junit.AfterClass;

+import org.junit.Before;

+import org.junit.BeforeClass;

+import org.junit.Test;

+import org.junit.runner.RunWith;

+import org.springframework.boot.test.context.SpringBootTest;

+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

+import org.springframework.boot.web.server.LocalServerPort;

+import org.springframework.test.context.ActiveProfiles;

+import org.springframework.test.context.TestPropertySource;

+import org.springframework.test.context.junit4.SpringRunner;

+

+@RunWith(SpringRunner.class)

+@SpringBootTest(

+    webEnvironment = WebEnvironment.RANDOM_PORT,

+    classes = {

+        Application.class

+    })

+@TestPropertySource("classpath:application-test.properties")

+@ActiveProfiles("test")

+public class HealthRouteIT {

+  @LocalServerPort

+  private int port;

+

+

+  @BeforeClass

+  public static void setup()throws Exception{

+    MemoryDatabase.setup();

+  }

+  @AfterClass

+  public static void cleanup(){

+    MemoryDatabase.cleanup();

+  }

+  @Before

+  public void setupRestAssured(){

+    RestAssured.port = port;

+    RestAssured.baseURI="https://localhost";

+    RestAssured.basePath="/otf/api";

+    RestAssured.urlEncodingEnabled =false;

+    RestAssured.useRelaxedHTTPSValidation();

+

+  }

+  @Test

+  public void testHealthRouteRespondsWith200(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/health/v1").then().assertThat().statusCode(200);

+  }

+  @Test

+  public void testHealthRouteRespondsWithUp(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/health/v1").then().assertThat().body("message", equalTo("UP"));

+  }

+  @Test

+  public void testHealthRouteRespondsWithJson(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/health/v1").then().contentType("application/json");

+  }

+

+

+}

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
index 0000000..a16a23c
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/InstanceServiceRouteIT.java
@@ -0,0 +1,170 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.integration.services;

+

+import static org.hamcrest.CoreMatchers.containsString;

+import static org.hamcrest.CoreMatchers.equalTo;

+

+import org.oran.otf.api.Application;

+import org.oran.otf.api.tests.shared.MemoryDatabase;

+import org.oran.otf.common.model.TestDefinition;

+import org.oran.otf.common.model.TestInstance;

+import org.oran.otf.common.model.User;

+import io.restassured.RestAssured;

+import org.eclipse.jetty.http.QuotedQualityCSV;

+import org.junit.AfterClass;

+import org.junit.Before;

+import org.junit.BeforeClass;

+import org.junit.Test;

+import org.junit.runner.RunWith;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.beans.factory.annotation.Value;

+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

+import org.springframework.boot.test.context.SpringBootTest;

+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

+import org.springframework.boot.web.server.LocalServerPort;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.test.context.ActiveProfiles;

+import org.springframework.test.context.TestPropertySource;

+import org.springframework.test.context.junit4.SpringRunner;

+

+@RunWith(SpringRunner.class)

+@SpringBootTest(

+    webEnvironment = WebEnvironment.RANDOM_PORT,

+    classes = {Application.class}

+)

+@TestPropertySource("classpath:application-test.properties")

+@ActiveProfiles("test")

+public class InstanceServiceRouteIT {

+  @LocalServerPort

+  private int port;

+  @Value("${otf.mechid}")

+  private String username;

+  @Value("${otf.mechpass}")

+  private String password;

+  private static User mechUser;

+

+  @Autowired

+  private MongoTemplate mongoTemplate;

+

+  @BeforeClass

+  public static void setup() throws Exception{

+    MemoryDatabase.createAllTables();

+    MemoryDatabase.createAllAdmin();

+    //mechUser = MemoryDatabase.createMechUser();

+  }

+  @AfterClass

+  public static void cleanup(){

+    MemoryDatabase.cleanup();

+  }

+  @Before

+  public void setupRestAssured() {

+    RestAssured.port = port;

+    RestAssured.urlEncodingEnabled = false;

+    RestAssured.baseURI = "https://localhost";

+    RestAssured.basePath="/otf/api/testInstance";

+    RestAssured.useRelaxedHTTPSValidation();

+  }

+  //NoAuth Tests

+

+  @Test

+  public void testFindByIdRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/v1/id/abced").then().assertThat().statusCode(401);

+  }

+  @Test

+  public void testFindByProcessKeyRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/v1/processDefinitionKey/abced").then().assertThat().statusCode(401);

+  }

+  @Test

+  public void testFindByProcessKeyAndVersionRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/v1/processDefinitionKey/abced/version/1").then().assertThat().statusCode(401);

+  }

+

+

+  @Test

+  public void testExecuteRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/execute/v1/id/abced/").then().assertThat().statusCode(401);

+  }

+

+

+  @Test

+  public void testCreateByTestDefinitionIdRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/create/v1/testDefinitionId/abced/").then().assertThat().statusCode(401);

+  }

+  @Test

+  public void testCreateByTestDefinitionIdAndVersionRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/create/v1/testDefinitionId/abced/version/2").then().assertThat().statusCode(401);

+  }

+  @Test

+  public void testCreateByProcessDefinitionKeyRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/create/v1/processDefinitionKey/abced").then().assertThat().statusCode(401);

+  }

+  @Test

+  public void testCreateByProcessDefinitionKeyAndVersionRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/create/v1/processDefinitionKey/abced/version/2").then().assertThat().statusCode(401);

+  }

+

+  //With Auth and Wrong id

+  @Test

+  public void testFindByIdRespondsWith400OnWrongId(){

+    RestAssured.given().auth().basic(username,password).log().all().header("Accept", "application/json").get("/v1/id/abced").then().assertThat().statusCode(400);

+  }

+  @Test

+  public void testFindByIdRespondsWithMessageOnWrongId(){

+    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)"));

+  }

+  @Test

+  public void testFindByProcessDefinitionRespondsWith400OnWrongProcessDefinition(){

+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("testDef1")), TestDefinition.class);

+    RestAssured.given().auth().basic(username,password).log().all().header("Accept", "*/*").get("/v1/processDefinitionKey/"+testDefinition.getProcessDefinitionKey()).then().assertThat().statusCode(400);

+  }

+  @Test

+  public void testFindByProcessDefinitionRespondsWithMessageOnWrongProcessDefinition(){

+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("testDef1")), TestDefinition.class);

+    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"));

+  }

+

+  //Successful Get Methods

+

+  @Test

+  public void testFindByIdRespondsWith200OnSuccess(){

+    TestInstance testInstance = mongoTemplate.findOne(new Query(Criteria.where("testInstanceName").is("MechTestInstance")), TestInstance.class);

+    RestAssured.given().auth().basic(username,password).log().all().header("Accept", "*/*").get("/v1/id/"+testInstance.get_id()).then().assertThat().statusCode(200);

+  }

+

+  @Test

+  public void testFindByProcessDefinitionKeyRespondsWith200OnSuccess(){

+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("MechTestDefinition")), TestDefinition.class);

+    RestAssured.given().auth().basic(username, password).log().all().header("Accept", "*/*").get("/v1/processDefinitionKey/"+testDefinition.getProcessDefinitionKey()).then().assertThat().statusCode(200);

+  }

+

+  @Test

+  public void testFindByProcessDefinitionKeyAndVersionRespondsWith200OnSuccess(){

+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("MechTestDefinition")), TestDefinition.class);

+    RestAssured.given().auth().basic(username, password).log().all().header("Accept", "*/*").get("/v1/processDefinitionKey/"+testDefinition.getProcessDefinitionKey()+"/version/"+1).then().assertThat().statusCode(200);

+  }

+

+  @Test

+  public void testCreateByTestDefinitionIdRespondsWith201OnSuccess(){

+    TestDefinition testDefinition = mongoTemplate.findOne(new Query(Criteria.where("testName").is("MechTestDefinition")), TestDefinition.class);

+    System.out.println(testDefinition.getBpmnInstances());

+    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);

+  }

+

+}

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
index 0000000..132464a
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/OtfOpenRouteIT.java
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.integration.services;

+

+import org.oran.otf.api.Application;

+import org.oran.otf.api.tests.shared.MemoryDatabase;

+import io.restassured.RestAssured;

+import org.junit.AfterClass;

+import org.junit.Before;

+import org.junit.BeforeClass;

+import org.junit.Ignore;

+import org.junit.Test;

+import org.junit.runner.RunWith;

+import org.springframework.boot.test.context.SpringBootTest;

+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

+import org.springframework.boot.web.server.LocalServerPort;

+import org.springframework.test.context.ActiveProfiles;

+import org.springframework.test.context.TestPropertySource;

+import org.springframework.test.context.junit4.SpringRunner;

+

+@RunWith(SpringRunner.class)

+@SpringBootTest(

+    webEnvironment = WebEnvironment.RANDOM_PORT,

+    classes = {Application.class}

+)

+@TestPropertySource("classpath:application-test.properties")

+@ActiveProfiles("test")

+public class OtfOpenRouteIT {

+  @LocalServerPort

+  private int port;

+

+  @BeforeClass

+  public static void setup() throws Exception{

+    MemoryDatabase.setup();

+  }

+  @AfterClass

+  public static void cleanup(){

+    MemoryDatabase.cleanup();

+  }

+  @Before

+  public void setupRestAssured(){

+    RestAssured.port =port;

+    RestAssured.urlEncodingEnabled = false;

+    RestAssured.baseURI="https://localhost";

+    RestAssured.basePath="/otf/api";

+    RestAssured.useRelaxedHTTPSValidation();

+  }

+  @Ignore("Ignoring test because it fails since it tries to request to specific port, uncomment to view error")

+  @Test

+  public void testOtfOpenRouteRespondsWith200(){

+    RestAssured.given().log().all().header("Accept", "application/json").get("/demo/openapi.json").then().statusCode(200);

+  }

+}

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
index 0000000..6186d3a
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/Permissions/PermissionServiceIT.java
@@ -0,0 +1,331 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.integration.services.Permissions;

+

+import org.oran.otf.api.Application;

+import org.oran.otf.api.tests.shared.MemoryDatabase;

+import org.oran.otf.common.model.Group;

+import org.oran.otf.common.model.GroupMember;

+import org.oran.otf.common.model.User;

+import org.oran.otf.common.repository.GroupRepository;

+import org.oran.otf.common.utility.permissions.PermissionChecker;

+import org.oran.otf.common.utility.permissions.PermissionUtil;

+import org.oran.otf.common.utility.permissions.UserPermission;

+import org.bson.types.ObjectId;

+import org.junit.*;

+import org.junit.runner.RunWith;

+import org.mockito.Mockito;

+import org.springframework.beans.factory.annotation.Autowired;

+import org.springframework.boot.test.context.SpringBootTest;

+import org.springframework.test.context.ActiveProfiles;

+import org.springframework.test.context.TestPropertySource;

+import org.springframework.test.context.junit4.SpringRunner;

+

+import java.util.*;

+

+@RunWith(SpringRunner.class)

+@SpringBootTest(

+        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,

+        classes = {

+                Application.class,

+        })

+@TestPropertySource("classpath:application-test.properties")

+@ActiveProfiles("test")

+public class PermissionServiceIT {

+    @Autowired

+    private GroupRepository groupRepository;

+    private List<Group> groups;

+    private Group parentGroup;

+    private Group firstChildGroup;

+    private Group childOfChildGroup;

+

+    @BeforeClass

+    public static void setUp() throws Exception{

+        MemoryDatabase.setup();

+        MemoryDatabase.createGroupsForPermission();

+    }

+    @Before

+    public void setUpGroups()

+    {

+        groups = groupRepository.findAll();

+        parentGroup = groupRepository.findFirstByGroupName("parent group");

+        firstChildGroup = groupRepository.findFirstByGroupName("first child group");

+        childOfChildGroup = groupRepository.findFirstByGroupName("child of child group");

+    }

+    @AfterClass

+    public static void cleanup(){

+        MemoryDatabase.cleanup();

+    }

+    /*

+     if this test failed there was a error during set up so ignore the failures produced by other tests til this pass

+    */

+    @Test

+    public void setUpTest(){

+        List<Group> groups = groupRepository.findAll();

+        parentGroup = groupRepository.findFirstByGroupName("parent group");

+        firstChildGroup = groupRepository.findFirstByGroupName("first child group");

+        childOfChildGroup = groupRepository.findFirstByGroupName("child of child group");

+        Assert.assertNotNull(groups);

+        Assert.assertFalse(groups.isEmpty());

+

+        Assert.assertNotNull(parentGroup.getMembers());

+        Assert.assertFalse(parentGroup.getMembers().isEmpty());

+        Assert.assertNotNull(parentGroup.getRoles());

+        Assert.assertFalse(parentGroup.getRoles().isEmpty());

+

+        Assert.assertNotNull(firstChildGroup.getMembers());

+        Assert.assertFalse(firstChildGroup.getMembers().isEmpty());

+        Assert.assertNotNull(firstChildGroup.getRoles());

+        Assert.assertFalse(firstChildGroup.getRoles().isEmpty());

+

+        Assert.assertNotNull(childOfChildGroup.getMembers());

+        Assert.assertFalse(childOfChildGroup.getMembers().isEmpty());

+        Assert.assertNotNull(childOfChildGroup.getRoles());

+        Assert.assertFalse(childOfChildGroup.getRoles().isEmpty());

+        // all groups are set up with 1 member in memory db

+        Assert.assertEquals(1,parentGroup.getMembers().size());

+        Assert.assertEquals(1,firstChildGroup.getMembers().size());

+        Assert.assertEquals(1,childOfChildGroup.getMembers().size());

+    }

+    @Test

+    public void findUserRoles(){

+        GroupMember parentMember = parentGroup.getMembers().get(0);

+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);

+        GroupMember childOfChildMember = childOfChildGroup.getMembers().get(0);

+

+        User parentUserMock = Mockito.mock(User.class);

+        User firstChildUserMock = Mockito.mock(User.class);

+        User childOfChildUserMock = Mockito.mock(User.class);

+

+        Mockito.when(parentUserMock.get_id()).thenReturn(parentMember.getUserId());

+        Mockito.when(firstChildUserMock.get_id()).thenReturn(firstChildMember.getUserId());

+        Mockito.when(childOfChildUserMock.get_id()).thenReturn(childOfChildMember.getUserId());

+

+        Set<String> parentMemberRoles = PermissionUtil.findUserRoles(parentUserMock, parentGroup);

+        Set<String> firstChildRoles = PermissionUtil.findUserRoles(firstChildUserMock, firstChildGroup);

+        Set<String> childOfChildRoles = PermissionUtil.findUserRoles(childOfChildUserMock, childOfChildGroup);

+

+        // all group members should only have 1 role (admin) set up except first child

+        Assert.assertEquals(1,parentMemberRoles.size());

+        Assert.assertTrue(parentMemberRoles.contains("admin"));

+        Assert.assertEquals(2,firstChildRoles.size());

+        Assert.assertTrue(firstChildRoles.contains("admin"));

+        Assert.assertTrue(firstChildRoles.contains("dev"));

+        Assert.assertEquals(1,childOfChildRoles.size());

+        Assert.assertTrue(childOfChildRoles.contains("executor"));

+

+        Assert.assertFalse(parentMemberRoles.contains("executor"));

+        Assert.assertFalse(firstChildRoles.contains("executor"));

+        Assert.assertFalse("should not have admin roles in child of child", childOfChildRoles.contains("admin"));

+    }

+    @Test

+    public void getRolePermissionsTest()

+    {

+        ObjectId firstChildId =firstChildGroup.getMembers().get(0).getUserId();

+        User firstChildUserMock = Mockito.mock(User.class);

+        Mockito.when(firstChildUserMock.get_id()).thenReturn(firstChildId);

+        Set<String> roles = PermissionUtil.findUserRoles(firstChildUserMock,firstChildGroup); //dev and admin roles only

+

+        Assert.assertEquals(2,roles.size());

+        for(String role : roles){

+            Set<String> permissions = PermissionUtil.getRolePermissions(role,parentGroup);

+            Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("READ"));

+            Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("WRITE"));

+            Assert.assertFalse("all permissions allowed except execute and delete",permissions.contains("DELETE"));

+            Assert.assertFalse("all permissions allowed except execute and delete",permissions.contains("EXECUTE"));

+        }

+    }

+    @Test

+    public void getUserGroupPermissionTest(){

+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);

+        User firstChildUser = Mockito.mock(User.class);

+        Mockito.when(firstChildUser.get_id()).thenReturn(firstChildMember.getUserId());

+        Set<String> permissions = PermissionUtil.getUserGroupPermissions(firstChildUser,firstChildGroup); // should include everything except execute and delete

+

+        Assert.assertEquals(3,permissions.size());

+        Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("READ"));

+        Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("WRITE"));

+        Assert.assertFalse("all permissions allowed except execute and delete",permissions.contains("DELETE"));

+        Assert.assertFalse("all permissions allowed except execute and delete",permissions.contains("EXECUTE"));

+        Assert.assertTrue("all permissions allowed except execute and delete",permissions.contains("MANAGEMENT"));

+    }

+

+    @Test

+    public void hasPermissionToTest(){

+        GroupMember parentMember = parentGroup.getMembers().get(0);

+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);

+        GroupMember childOfChildMember = childOfChildGroup.getMembers().get(0);

+

+        User parentGroupUser = Mockito.mock(User.class);

+        User firstChildUser = Mockito.mock(User.class);

+        User childOfChildUser =Mockito.mock(User.class);

+        Mockito.when(parentGroupUser.get_id()).thenReturn(parentMember.getUserId());

+        Mockito.when(firstChildUser.get_id()).thenReturn(firstChildMember.getUserId());

+        Mockito.when(childOfChildUser.get_id()).thenReturn(childOfChildMember.getUserId());

+

+        String read = "read";

+        String write= "write";

+        String manage = "management";

+        String delete = "delete";

+        String execute= "execute";

+

+        Assert.assertTrue(PermissionUtil.hasPermissionTo(read,parentGroupUser,parentGroup));

+        Assert.assertTrue(PermissionUtil.hasPermissionTo(write,parentGroupUser,parentGroup));

+        Assert.assertTrue(PermissionUtil.hasPermissionTo(manage,parentGroupUser,parentGroup));

+        Assert.assertFalse(PermissionUtil.hasPermissionTo(delete,parentGroupUser,parentGroup));

+        Assert.assertFalse(PermissionUtil.hasPermissionTo(execute,parentGroupUser,parentGroup));

+

+        Assert.assertTrue(PermissionUtil.hasPermissionTo(read,firstChildUser,firstChildGroup));

+        Assert.assertTrue(PermissionUtil.hasPermissionTo(write,firstChildUser,firstChildGroup));

+        Assert.assertTrue(PermissionUtil.hasPermissionTo(manage,firstChildUser,firstChildGroup));

+        Assert.assertFalse(PermissionUtil.hasPermissionTo(delete,firstChildUser,firstChildGroup));

+        Assert.assertFalse(PermissionUtil.hasPermissionTo(execute,firstChildUser,firstChildGroup));

+

+        Assert.assertFalse(PermissionUtil.hasPermissionTo(read,childOfChildUser,childOfChildGroup));

+        Assert.assertFalse(PermissionUtil.hasPermissionTo(write,childOfChildUser,childOfChildGroup));

+        Assert.assertFalse(PermissionUtil.hasPermissionTo(manage,childOfChildUser,childOfChildGroup));

+        Assert.assertFalse(PermissionUtil.hasPermissionTo(delete,childOfChildUser,childOfChildGroup));

+        Assert.assertTrue(PermissionUtil.hasPermissionTo(execute,childOfChildUser,childOfChildGroup));

+    }

+    @Test

+    public void buildUserPermissionTest()

+    {

+        /*

+           should be the following format

+           parent members:

+           parentGroup = {read,write,management}

+           first Child group = {read}

+           child of child group = {read}

+

+           first child group:

+           parentGroup = {read}

+           first Child group = {read,write,management}

+           child of child group = {read}

+

+           child of child:

+           parentGroup = {read}

+           first Child group = {read}

+           child of child group = {execute}

+         */

+

+        GroupMember parentMember = parentGroup.getMembers().get(0);

+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);

+        GroupMember childOfChildMember = childOfChildGroup.getMembers().get(0);

+

+        User parentGroupUser = Mockito.mock(User.class);

+        User firstChildUser = Mockito.mock(User.class);

+        User childOfChildUser =Mockito.mock(User.class);

+        Mockito.when(parentGroupUser.get_id()).thenReturn(parentMember.getUserId());

+        Mockito.when(firstChildUser.get_id()).thenReturn(firstChildMember.getUserId());

+        Mockito.when(childOfChildUser.get_id()).thenReturn(childOfChildMember.getUserId());

+

+        String read = "READ";

+        String write= "WRITE";

+        String manage = "MANAGEMENT";

+        String delete = "DELETE";

+        String execute= "EXECUTE";

+

+        UserPermission parentUserPermissions = new PermissionUtil().buildUserPermission(parentGroupUser,groupRepository);

+        UserPermission firstChildUserPermissions = new PermissionUtil().buildUserPermission(firstChildUser,groupRepository);

+        UserPermission childOfChildUserPermissions = new PermissionUtil().buildUserPermission(childOfChildUser,groupRepository);

+        Map<String,Set<String>> parentAccessControl = parentUserPermissions.getUserAccessMap();

+        Map<String,Set<String>> firstChildAccessControl = firstChildUserPermissions.getUserAccessMap();

+        Map<String,Set<String>> childOfChildAccessControl = childOfChildUserPermissions.getUserAccessMap();

+

+        //test for parent access control

+        Assert.assertTrue(parentAccessControl.get(parentGroup.get_id().toString()).contains(read));

+        Assert.assertTrue(parentAccessControl.get(parentGroup.get_id().toString()).contains(write));

+        Assert.assertTrue(parentAccessControl.get(parentGroup.get_id().toString()).contains(manage));

+        //test all access is passed to firstChildGroup

+        Assert.assertTrue(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(read));

+        Assert.assertTrue(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(write));

+        Assert.assertTrue(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(manage));

+        //test all access is passed to child of child group

+        Assert.assertTrue(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(read));

+        Assert.assertTrue(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(write));

+        Assert.assertTrue(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(manage));

+        // make sure parent user dont have other permissions in first child group

+        Assert.assertFalse(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(delete));

+        Assert.assertFalse(parentAccessControl.get(firstChildGroup.get_id().toString()).contains(execute));

+        //test that parent dont have other permissions in child of child group

+        Assert.assertFalse(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(delete));

+        Assert.assertFalse(parentAccessControl.get(childOfChildGroup.get_id().toString()).contains(execute));

+

+        //test for first child access control

+        Assert.assertTrue(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(read));

+        Assert.assertTrue(firstChildAccessControl.get(firstChildGroup.get_id().toString()).contains(read));

+        Assert.assertTrue(firstChildAccessControl.get(firstChildGroup.get_id().toString()).contains(write));

+        Assert.assertTrue(firstChildAccessControl.get(firstChildGroup.get_id().toString()).contains(manage));

+        // test that first child group get passed to child of child

+        Assert.assertTrue(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(read));

+        Assert.assertTrue(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(write));

+        Assert.assertTrue(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(manage));

+        // make sure firstchild user dont have other permissions

+        Assert.assertFalse(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(write));

+        Assert.assertFalse(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(manage));

+        Assert.assertFalse(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(delete));

+        Assert.assertFalse(firstChildAccessControl.get(parentGroup.get_id().toString()).contains(execute));

+        // test to confirm no extra permission is passed to child of child

+        Assert.assertFalse(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(delete));

+        Assert.assertFalse(firstChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(execute));

+

+        //test for child of child access control

+        Assert.assertTrue(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(read));

+        Assert.assertTrue(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(read));

+        Assert.assertTrue(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(execute));

+        // make sure child of child user dont have other permissions

+        Assert.assertFalse(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(write));

+        Assert.assertFalse(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(manage));

+        Assert.assertFalse(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(delete));

+        Assert.assertFalse(childOfChildAccessControl.get(parentGroup.get_id().toString()).contains(execute));

+

+        Assert.assertFalse(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(write));

+        Assert.assertFalse(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(manage));

+        Assert.assertFalse(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(delete));

+        Assert.assertFalse(childOfChildAccessControl.get(firstChildGroup.get_id().toString()).contains(execute));

+

+        Assert.assertFalse(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(write));

+        Assert.assertFalse(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(manage));

+        Assert.assertFalse(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(delete));

+        Assert.assertFalse(childOfChildAccessControl.get(childOfChildGroup.get_id().toString()).contains(read));

+    }

+    @Test

+    public void basicTest(){

+        GroupMember parentMember = parentGroup.getMembers().get(0);

+        GroupMember firstChildMember = firstChildGroup.getMembers().get(0);

+        GroupMember childOfChildMember = childOfChildGroup.getMembers().get(0);

+

+        User parentGroupUser = Mockito.mock(User.class);

+        User firstChildUser = Mockito.mock(User.class);

+        User childOfChildUser =Mockito.mock(User.class);

+        Mockito.when(parentGroupUser.get_id()).thenReturn(parentMember.getUserId());

+        Mockito.when(firstChildUser.get_id()).thenReturn(firstChildMember.getUserId());

+        Mockito.when(childOfChildUser.get_id()).thenReturn(childOfChildMember.getUserId());

+

+        Assert.assertTrue(PermissionChecker.hasPermissionTo(childOfChildUser,firstChildGroup,UserPermission.Permission.READ,groupRepository));

+        Assert.assertTrue(PermissionChecker.hasPermissionTo(childOfChildUser,parentGroup,UserPermission.Permission.READ,groupRepository));

+        Assert.assertFalse(PermissionChecker.hasPermissionTo(childOfChildUser,childOfChildGroup,UserPermission.Permission.READ,groupRepository));

+

+        Assert.assertFalse(PermissionChecker.hasPermissionTo(childOfChildUser,firstChildGroup,UserPermission.Permission.EXECUTE,groupRepository));

+        Assert.assertTrue(PermissionChecker.hasPermissionTo(firstChildUser,firstChildGroup,UserPermission.Permission.WRITE,groupRepository));

+        Assert.assertFalse(PermissionChecker.hasPermissionTo(firstChildUser,firstChildGroup,UserPermission.Permission.EXECUTE,groupRepository));

+

+        Assert.assertFalse(PermissionChecker.hasPermissionTo(parentGroupUser,parentGroup,UserPermission.Permission.DELETE,groupRepository));

+        Assert.assertFalse(PermissionChecker.hasPermissionTo(parentGroupUser,parentGroup,UserPermission.Permission.EXECUTE,groupRepository));

+        Assert.assertFalse(PermissionChecker.hasPermissionTo(parentGroupUser,firstChildGroup,UserPermission.Permission.EXECUTE,groupRepository));

+    }

+}

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
index 0000000..7cd2b43
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/integration/services/StrategyServiceRouteIT.java
@@ -0,0 +1,74 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.integration.services;

+

+import org.oran.otf.api.Application;

+import org.oran.otf.api.tests.shared.MemoryDatabase;

+import io.restassured.RestAssured;

+import org.junit.AfterClass;

+import org.junit.Before;

+import org.junit.BeforeClass;

+import org.junit.Test;

+import org.junit.runner.RunWith;

+import org.springframework.boot.test.context.SpringBootTest;

+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

+import org.springframework.boot.web.server.LocalServerPort;

+import org.springframework.test.context.ActiveProfiles;

+import org.springframework.test.context.TestPropertySource;

+import org.springframework.test.context.junit4.SpringRunner;

+

+@RunWith(SpringRunner.class)

+@SpringBootTest(

+    webEnvironment = WebEnvironment.RANDOM_PORT,

+    classes = {Application.class}

+)

+@TestPropertySource("classpath:application-test.properties")

+@ActiveProfiles("test")

+public class StrategyServiceRouteIT {

+  @LocalServerPort

+  private int port;

+  @BeforeClass

+  public static void setup() throws Exception{

+    MemoryDatabase.setup();

+  }

+  @AfterClass

+  public static void cleanup(){

+    MemoryDatabase.cleanup();

+  }

+  @Before

+  public void setupRestAssured(){

+    RestAssured.port = port;

+    RestAssured.baseURI="https://localhost";

+    RestAssured.basePath="/otf/api/testStrategy";

+    RestAssured.urlEncodingEnabled=false;

+    RestAssured.useRelaxedHTTPSValidation();

+  }

+

+  @Test

+  public void testStrategyServiceRouteDeployRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").post("/deploy/v1").then().assertThat().statusCode(401);

+  }

+  @Test

+  public void testStrategyServiceRouteDeleteByTestDefinitionIdRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").delete("/delete/v1/testDefinitionId/56565656").then().assertThat().statusCode(401);

+  }

+  @Test

+  public void testStrategyServiceRouteDeleteByDeploymentIdRespondsWith401OnNoAuth(){

+    RestAssured.given().log().all().header("Accept", "application/json").delete("/delete/v1/deploymentId/54545454").then().assertThat().statusCode(401);

+  }

+

+}

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
index 0000000..2c17abb
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/shared/MemoryDatabase.java
@@ -0,0 +1,386 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.shared;

+

+import org.oran.otf.common.model.*;

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.oran.otf.common.model.local.UserGroup;

+import com.mongodb.BasicDBObjectBuilder;

+import com.mongodb.DBObject;

+import com.mongodb.MongoClient;

+import de.flapdoodle.embed.mongo.Command;

+import de.flapdoodle.embed.mongo.MongodExecutable;

+import de.flapdoodle.embed.mongo.MongodProcess;

+import de.flapdoodle.embed.mongo.MongodStarter;

+import de.flapdoodle.embed.mongo.config.DownloadConfigBuilder;

+import de.flapdoodle.embed.mongo.config.ExtractedArtifactStoreBuilder;

+import de.flapdoodle.embed.mongo.config.IMongodConfig;

+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;

+import de.flapdoodle.embed.mongo.config.Net;

+import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;

+import de.flapdoodle.embed.mongo.distribution.Version;

+import de.flapdoodle.embed.mongo.distribution.Version.Main;

+import de.flapdoodle.embed.process.config.IRuntimeConfig;

+import de.flapdoodle.embed.process.config.store.HttpProxyFactory;

+import de.flapdoodle.embed.process.runtime.Network;

+

+import java.sql.Timestamp;

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.Date;

+import java.util.List;

+import java.util.Random;

+import javassist.util.proxy.ProxyFactory;

+import org.apache.commons.lang3.time.DateUtils;

+import org.apache.http.HttpResponse;

+import org.apache.http.client.methods.HttpGet;

+import org.oran.otf.common.model.*;

+import org.springframework.context.annotation.Configuration;

+import org.bson.types.ObjectId;

+import org.springframework.data.mongodb.core.MongoTemplate;

+import org.springframework.data.mongodb.core.query.Criteria;

+import org.springframework.data.mongodb.core.query.Query;

+import org.springframework.test.context.ActiveProfiles;

+

+

+@ActiveProfiles("test")

+public abstract class MemoryDatabase {

+  protected static MongodExecutable mongodExecutable;

+  protected static MongoTemplate mongoTemplate;

+

+  //TODO use mongod process to be response from mongodExecutable.start(), on pulbic calls check if null if so call setup else dont

+  protected static MongodProcess mongod = null;

+

+  protected static Query userQuery = new Query(Criteria.where("firstName").is("Mech"));

+  //protected static Query mechUserQuery = new Query(Criteria.where("firstName").is("Mech"));

+  protected static Query testInstanceQuery = new Query(Criteria.where("testInstanceName").is("MechTestInstance"));

+  protected static Query groupQuery = new Query(Criteria.where("groupName").is("MechGroup"));

+  protected static Query testDefQuery = new Query(Criteria.where("testName").is("MechTestDefinition"));

+

+  //values should match with DataConfig2

+  protected static int port=5555;

+  protected static String host="localhost";

+

+

+  public static void setup()throws Exception{

+    Command command = Command.MongoD;

+    IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()

+        .defaults(command)

+        .artifactStore(new ExtractedArtifactStoreBuilder()

+            .defaults(command)

+            .download(new DownloadConfigBuilder()

+                .defaultsForCommand(command)

+                .proxyFactory(new HttpProxyFactory("localhost",8080))))

+             .build();

+

+    //String host = "localhost";

+    //int port = 5555;

+

+    IMongodConfig mongodConfig = new MongodConfigBuilder().version(Main.PRODUCTION)

+        .net(new Net(host, port, Network.localhostIsIPv6()))

+        .build();

+    //MongodStarter starter = MongodStarter.getDefaultInstance();

+    MongodStarter starter = MongodStarter.getInstance(runtimeConfig);

+    mongodExecutable = starter.prepare(mongodConfig);

+    mongodExecutable.start();

+    mongoTemplate = new MongoTemplate(new MongoClient(host, port), "test");

+

+    DBObject objectToSave = BasicDBObjectBuilder.start()

+        .add("name", "john")

+        .get();

+    mongoTemplate.save(objectToSave, "collection");

+

+

+  }

+  /*

+  public static User createMechUser(){

+

+    User user = mongoTemplate.findOne(mechUserQuery, User.class);

+    if(user == null) {

+      user = new User();

+      user.setFirstName("Mech");

+      user.setLastName("Id");

+      user.setEmail("email@localhost");

+      mongoTemplate.save(user, "users");

+      user = mongoTemplate.findOne(mechUserQuery, User.class);

+    }

+    return user;

+  }

+

+   */

+  //TODO: make admin user be the mechid, this is because of AAF test will fail if random user is used

+  private static User createMechUserIfNotExists(){

+    User user = mongoTemplate.findOne(userQuery, User.class);

+    if(user == null) {

+      user = new User();

+      user.setFirstName("Mech");

+      user.setLastName("Id");

+      user.setEmail("email@localhost");

+      mongoTemplate.save(user, "users");

+      user = mongoTemplate.findOne(userQuery, User.class);

+    }

+    return user;

+

+  }

+  private static Group createMechGroupIfNotExists(){

+    User user = MemoryDatabase.createMechUserIfNotExists();

+    Group group = mongoTemplate.findOne(groupQuery, Group.class);

+    if(group == null) {

+      String groupName = "MechGroup";

+      group = new Group();

+      group.setOwnerId(user.get_id());

+      group.setGroupName(groupName);

+      group.setGroupDescription(groupName + " description");

+      mongoTemplate.save(group, "groups");

+      group = mongoTemplate.findOne(groupQuery, Group.class);

+    }

+    return group;

+  }

+  private static TestDefinition createMechTestDefinitionIfNotExists(){

+    TestDefinition testDefinition = mongoTemplate.findOne(testDefQuery, TestDefinition.class);

+    if(testDefinition == null){

+

+      BpmnInstance bpmnInstance = new BpmnInstance();

+      bpmnInstance.setDeployed(true);

+      bpmnInstance.setVersion(1);

+      List list = new ArrayList(Arrays.asList(bpmnInstance));

+

+      testDefinition = new TestDefinition();

+      testDefinition.setDisabled(false);

+      testDefinition.setBpmnInstances(list);

+      testDefinition.setTestName("MechTestDefinition");

+      testDefinition.setTestDescription("MechTestDefinition description");

+      testDefinition.setProcessDefinitionKey("MechTestDefinitionKey");

+      testDefinition.setCreatedBy(createMechUserIfNotExists().get_id());

+      testDefinition.setGroupId(createMechGroupIfNotExists().get_id());

+      testDefinition.setCreatedAt(new Timestamp(new Date().getTime()));

+      testDefinition.setUpdatedAt(new Timestamp(new Date().getTime()));

+      mongoTemplate.save(testDefinition, "testDefinitions");

+      testDefinition = mongoTemplate.findOne(testDefQuery, TestDefinition.class);

+    }

+    return testDefinition;

+

+  }

+

+

+  private static TestInstance createMechTestInstanceIfNotExists(){

+    TestInstance testInstance = mongoTemplate.findOne(testInstanceQuery, TestInstance.class);

+    User user = createMechUserIfNotExists();

+    UserGroup userGroup = new UserGroup();

+    if(testInstance == null){

+      testInstance = new TestInstance();

+      testInstance.setTestInstanceName("MechTestInstance");

+      testInstance.setTestInstanceDescription("MechTestInstance description");

+      testInstance.setCreatedBy(user.get_id());

+      testInstance.setGroupId(createMechGroupIfNotExists().get_id());

+      testInstance.setTestDefinitionId(createMechTestDefinitionIfNotExists().get_id());

+      testInstance.setMaxExecutionTimeInMillis(new Random().nextInt(5000));

+      testInstance.setUseLatestTestDefinition(true);

+      mongoTemplate.save(testInstance, "testInstances");

+      testInstance = mongoTemplate.findOne(testInstanceQuery, TestInstance.class);

+    }

+    userGroup.setGroupId(testInstance.getGroupId());

+    userGroup.setPermissions(Arrays.asList("Admin"));

+    user.setGroups(Arrays.asList(userGroup));

+    mongoTemplate.save(user, "users");

+    return testInstance;

+  }

+

+  public static void createGroups(){

+

+    MemoryDatabase.createMechUserIfNotExists();

+    List<String> groupNames = new ArrayList<>(Arrays.asList("Group1", "Group2", "Group3", "Group4", "Group5"));

+    groupNames.forEach(name->{

+      Group group = new Group();

+      User usr = mongoTemplate.findOne(userQuery, User.class);

+      group.setOwnerId(usr.get_id());

+      group.setGroupName(name);

+      group.setGroupDescription(name + " description");

+      mongoTemplate.save(group, "groups");

+    });

+

+  }

+

+  public static void createGroupsForPermission()

+  {

+    Group parentGroup = new Group();

+    Group firstChildGroup = new Group();

+    Group childOfChildGroup = new Group();

+    parentGroup.setGroupName("parent group");

+    firstChildGroup.setGroupName("first child group");

+    childOfChildGroup.setGroupName("child of child group");

+    Role adminRole = new Role();

+    Role devRole = new Role();

+    Role executorRole = new Role();

+    GroupMember parentMember = new GroupMember();

+    GroupMember firstChildMember = new GroupMember();

+    GroupMember childOfChildMember = new GroupMember();

+    //set up members

+    createUsers();

+    List<User> users = mongoTemplate.findAll(User.class,"users"); // this should be atleast 3 users

+    /*

+    set up

+    parent group -> members only with admin roles. Permission = "READ","WRITE","MANAGEMENT"

+    child group -> members only with admin and dev roles. Permission = "READ","WRITE", "MANAGEMENT

+    child of child group -> members with only executor roles. Permission = "EXECUTE

+     */

+    parentMember.setUserId(users.get(0).get_id());

+    parentMember.setRoles(Arrays.asList("admin"));

+    firstChildMember.setUserId(users.get(1).get_id());

+    firstChildMember.setRoles(Arrays.asList("dev","admin"));

+    childOfChildMember.setUserId(users.get(2).get_id());

+    childOfChildMember.setRoles(Arrays.asList("executor"));

+    //set up roles

+    adminRole.setRoleName("admin");

+    adminRole.setPermissions(Arrays.asList("READ","WRITE","MANAGEMENT"));

+    devRole.setRoleName("dev");

+    devRole.setPermissions(Arrays.asList("READ","WRITE"));

+    executorRole.setRoleName("executor");

+    executorRole.setPermissions(Arrays.asList("EXECUTE"));

+    List<Role> defaultRoles = new ArrayList<>();

+    defaultRoles.add(devRole);

+    defaultRoles.add(adminRole);

+    defaultRoles.add(executorRole);

+    //set up groups

+    parentGroup.setRoles(defaultRoles);

+    parentGroup.setMembers(Arrays.asList(parentMember));

+    firstChildGroup.setRoles(defaultRoles);

+    firstChildGroup.setMembers(Arrays.asList(firstChildMember));

+    childOfChildGroup.setRoles(defaultRoles);

+    childOfChildGroup.setMembers(Arrays.asList(childOfChildMember));

+    /*

+      set up parent tree

+      structure:

+      parentGroup

+          |

+      Child group

+          |

+      Child of child group

+     */

+    mongoTemplate.save(parentGroup,"groups");

+    mongoTemplate.save(firstChildGroup,"groups");

+    mongoTemplate.save(childOfChildGroup,"groups");

+    // query object so we can get the object id and set up parent ids

+    Query parentQ = new Query(Criteria.where("groupName").is("parent group"));

+    Query firstChildQ = new Query(Criteria.where("groupName").is("first child group"));

+    Query childOfChildQ = new Query(Criteria.where("groupName").is("child of child group"));

+    Group parentGroupDbObj = mongoTemplate.findOne(parentQ,Group.class);

+    Group firstChildDbObj = mongoTemplate.findOne(firstChildQ,Group.class);

+    Group childOfChildDbObj = mongoTemplate.findOne(childOfChildQ,Group.class);

+

+    firstChildDbObj.setParentGroupId(parentGroupDbObj.get_id());

+    childOfChildDbObj.setParentGroupId(firstChildDbObj.get_id());

+    mongoTemplate.save(firstChildDbObj);

+    mongoTemplate.save(childOfChildDbObj);

+  }

+

+  public static void createUsers(){

+    List<String> usersFirstNames = new ArrayList<>(Arrays.asList("Joe", "Jim", "Rick", "David", "Tony"));

+    List<String> usersLastNames = new ArrayList<>(Arrays.asList("Terry", "Roll", "Luis", "Perry"));

+      usersFirstNames.forEach(name->{

+        User user = new User();

+        int index = new Random().nextInt(usersFirstNames.size()-1);

+        user.setEmail(name+usersLastNames.get(index)+"@email.com");

+        user.setLastName(name);

+        user.setFirstName(usersLastNames.get(index));

+        mongoTemplate.save(user, "users");

+    });

+

+  }

+  public static void createTeatHeads(){

+    List<String> testheadNames = new ArrayList<>(Arrays.asList("SSH", "FTP", "PING", "PROCESS", "daRudeSandstorm"));

+    testheadNames.forEach(name->{

+      String random = Integer.toString(new Random().nextInt(4000)+4000);

+      TestHead testHead = new TestHead();

+      testHead.setTestHeadName(name);

+      testHead.setTestHeadDescription(name+" virtual test head ");

+      testHead.setPort(random);

+      testHead.setResourcePath("resources.vths.com/"+name);

+      testHead.setHostname("resources.vths.com");

+      testHead.setGroupId(createMechUserIfNotExists().get_id());

+      testHead.setGroupId(createMechGroupIfNotExists().get_id());

+      testHead.setCreatedAt(new Timestamp(new Date().getTime()));

+      testHead.setUpdatedAt(new Timestamp(new Date().getTime()));

+      mongoTemplate.save(testHead, "testHeads");

+

+    });

+  }

+  public static void createTestDefinitions(){

+    List<String> testDefinitionNames = new ArrayList<>(Arrays.asList("testDef1", "testDef2", "testDef3", "testDef4"));

+    testDefinitionNames.forEach(name->{

+      TestDefinition testDefinition = new TestDefinition();

+      testDefinition.setDisabled(false);

+      testDefinition.setTestName(name);

+      testDefinition.setTestDescription(name+" description");

+      testDefinition.setProcessDefinitionKey(name+"key");

+      testDefinition.setCreatedBy(createMechUserIfNotExists().get_id());

+      testDefinition.setGroupId(createMechGroupIfNotExists().get_id());

+      testDefinition.setCreatedAt(new Timestamp(new Date().getTime()));

+      testDefinition.setUpdatedAt(new Timestamp(new Date().getTime()));

+      mongoTemplate.save(testDefinition, "testDefinitions");

+    });

+  }

+  public static void createTestInstances(){

+    List<String> testInstanceName = new ArrayList<>(Arrays.asList("TestInstance1", "TestInstance2", "TestInstance3", "TestInstance4"));

+    testInstanceName.forEach(name->{

+      TestInstance testInstance = new TestInstance();

+      testInstance.setTestInstanceName(name);

+      testInstance.setTestInstanceDescription(name+" description");

+      testInstance.setCreatedBy(createMechUserIfNotExists().get_id());

+      testInstance.setGroupId(createMechGroupIfNotExists().get_id());

+      testInstance.setTestDefinitionId(createMechTestDefinitionIfNotExists().get_id());

+      testInstance.setMaxExecutionTimeInMillis(new Random().nextInt(5000));

+      testInstance.setUseLatestTestDefinition(true);

+      mongoTemplate.save(testInstance, "testInstances");

+    });

+  }

+

+  public static void createTestExecutions(){

+    List<String> results = new ArrayList<>(Arrays.asList("COMPLETED", "FAILED", "PASSED", "INCOMPLETE"));

+    results.forEach(result->{

+      TestExecution testExecution = new TestExecution();

+      testExecution.setAsync(false);

+      testExecution.setExecutorId(createMechUserIfNotExists().get_id());

+      testExecution.setGroupId(createMechGroupIfNotExists().get_id());

+      testExecution.setStartTime(new Timestamp(new Date().getTime()));

+      testExecution.setEndTime(new Timestamp(DateUtils.addHours(new Date(),3).getTime()));

+      testExecution.setTestResult(result);

+      testExecution.setTestResultMessage("Process result is: "+ result);

+      mongoTemplate.save(testExecution, "testExecutions");

+    });

+  }

+

+  public static void createAllAdmin(){

+    createMechTestDefinitionIfNotExists();

+    createMechTestInstanceIfNotExists();

+  }

+

+  public static void createAllTables()throws Exception{

+    setup();

+    createUsers();

+    createGroups();

+    createTeatHeads();

+    createTestDefinitions();

+    createTestInstances();

+    createTestExecutions();

+  }

+

+  public static void cleanup(){

+    mongodExecutable.stop();

+  }

+

+}

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
index 0000000..be607d1
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/DefinitionTest.java
@@ -0,0 +1,82 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models;

+

+import org.oran.otf.common.model.TestDefinition;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class DefinitionTest {

+  private static TestDefinition testDefinition;

+

+  @BeforeClass

+  public static void setup(){

+    testDefinition = new TestDefinition();

+  }

+

+  @Test

+  public void testDefinitionHasTestNameField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("testName");

+  }

+

+  @Test

+  public void testDefinitionHasTestDescriptionField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("testDescription");

+  }

+  @Test

+  public void testDefinitionHasProcessDefinitionKeyField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("processDefinitionKey");

+  }

+  @Test

+  public void testDefinitionHasBpmnInstancesField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("bpmnInstances");

+  }

+  @Test

+  public void testDefinitionHasGroupIdField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("groupId");

+  }

+  @Test

+  public void testDefinitionHasCreatedAtField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("createdAt");

+  }

+  @Test

+  public void testDefinitionHasUpdateAtField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("updatedAt");

+  }

+  @Test

+  public void testDefinitionHasCreatedByField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("createdBy");

+  }

+  @Test

+  public void testDefinitionHasUpdatedByField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("updatedBy");

+  }

+  @Test

+  public void testDefinitionHasDisabledField(){

+    Assertions.assertThat(testDefinition).hasFieldOrProperty("disabled");

+  }

+

+

+

+

+

+

+

+

+

+}

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
index 0000000..966ee76
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/ExecutionTest.java
@@ -0,0 +1,107 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models;

+

+import org.oran.otf.common.model.TestExecution;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class ExecutionTest {

+  private static TestExecution testExecution;

+

+  @BeforeClass

+  public static void setup(){

+    testExecution = new TestExecution();

+  }

+

+  @Test

+  public void testExecutionHasGroupIdField(){

+    Assertions.assertThat(testExecution).hasFieldOrProperty("groupId");

+  }

+  @Test

+  public void testExecutionHasExecutorIdField(){

+    Assertions.assertThat(testExecution).hasFieldOrProperty("executorId");

+  }

+  @Test

+  public void testExecutionHasAsyncField(){

+    Assertions.assertThat(testExecution).hasFieldOrProperty("async");

+  }

+  @Test

+  public void testExecutionHasStartTimeField(){

+    Assertions.assertThat(testExecution).hasFieldOrProperty("startTime");

+  }

+  @Test

+  public void testExecutionHasEndTimeField(){

+    Assertions.assertThat(testExecution).hasFieldOrProperty("endTime");

+  }

+  @Test

+  public void testExecutionHasAsyncTopicField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("asyncTopic");

+  }

+  @Test

+  public void testExecutionHasBussinessKeyField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("businessKey");

+  }

+  @Test

+  public void testExecutionHasProcessInstanceIdField(){

+    Assertions.assertThat(testExecution).hasFieldOrProperty("processInstanceId");

+  }

+  @Test

+  public void testExecutionHasTestResultField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("testResult");

+  }

+  @Test

+  public void testExecutionHasTestResultMessageField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("testResultMessage");

+  }

+  @Test

+  public void testExecutionHasTestDetailsField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("testDetails");

+  }

+  @Test

+  public void testExecutionHasTestHeadResultsField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("testHeadResults");

+  }

+  @Test

+  public void testExecutionHasTestInstanceResultsField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("testInstanceResults");

+  }

+  @Test

+  public void testExecutionHasHistoricEmailField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("historicEmail");

+  }

+  @Test

+  public void testExecutionHasHistoricTestInstanceField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("historicTestInstance");

+  }

+  @Test

+  public void testExecutionHasHistoricTestDefinitionField(){

+

+    Assertions.assertThat(testExecution).hasFieldOrProperty("historicTestDefinition");

+  }

+

+}

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
index 0000000..c28a406
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/GroupTest.java
@@ -0,0 +1,50 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models;

+

+import static org.assertj.core.api.Assertions.assertThat;

+

+import org.oran.otf.common.model.Group;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+

+public class GroupTest {

+  private static Group group;

+  @BeforeClass

+  public static void setup(){

+    group = new Group();

+  }

+  @Test

+  public void testGroupHasNameField(){

+    assertThat(group).hasFieldOrProperty("groupName");

+  }

+  @Test

+  public void testGroupHasGroupDescriptionField(){

+    assertThat(group).hasFieldOrProperty("groupDescription");

+  }

+

+  @Test

+  public void testGroupHasMechanizedIdsField(){

+    assertThat(group).hasFieldOrProperty("mechanizedIds");

+  }

+

+  @Test

+  public void testGroupHasOwnerIdField(){

+    assertThat(group).hasFieldOrProperty("ownerId");

+  }

+}

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
index 0000000..00ba7ca
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/HeadTest.java
@@ -0,0 +1,72 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models;

+

+import org.oran.otf.common.model.TestHead;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class HeadTest {

+  private static TestHead testHead;

+

+  @BeforeClass

+  public static void setup(){

+    testHead = new TestHead();

+  }

+  @Test

+  public void testHeadHasTestHeadNameField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("testHeadName");

+  }

+  @Test

+  public void testHeadHasTestHeadDescriptionField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("testHeadDescription");

+  }

+  @Test

+  public void testHeadHasHostNameField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("hostname");

+  }

+  @Test

+  public void testHeadHasPortField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("port");

+  }

+  @Test

+  public void testHeadHasResourcePathField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("resourcePath");

+  }

+  @Test

+  public void testHeadHasCreatorIdField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("creatorId");

+  }

+  @Test

+  public void testHeadHasGroupIdField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("groupId");

+  }

+  @Test

+  public void testHeadHasCreatedAtField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("createdAt");

+  }

+  @Test

+  public void testHeadHasUpdatedAtField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("updatedAt");

+  }

+  @Test

+  public void testHeadHasUpdatedByField(){

+    Assertions.assertThat(testHead).hasFieldOrProperty("updatedBy");

+  }

+

+}

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
index 0000000..3f1a4be
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/InstanceTest.java
@@ -0,0 +1,104 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models;

+

+import org.oran.otf.common.model.TestInstance;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class InstanceTest {

+

+  private static TestInstance testInstance;

+

+  @BeforeClass

+  public static void setup(){

+    testInstance = new TestInstance();

+  }

+  @Test

+  public void testInstanceHasTestInstanceNameField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("testInstanceName");

+  }

+  @Test

+  public void testInstanceHasInstanceDescriptionField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("testInstanceDescription");

+  }

+  @Test

+  public void testInstanceHasGroupIdField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("groupId");

+  }

+  @Test

+  public void testInstanceHasTestDefinitionIdField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("testDefinitionId");

+  }

+  @Test

+  public void testInstanceHasProcessDefinitionIdField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("processDefinitionId");

+  }

+  @Test

+  public void testInstanceHasUseLatestTestDefinitionField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("useLatestTestDefinition");

+  }

+  @Test

+  public void testInstanceHasDisabledField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("disabled");

+  }

+  @Test

+  public void testInstanceHasSimulationModeField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("simulationMode");

+  }

+  @Test

+  public void testInstanceHasMaxExecutionTimeInMillisField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("maxExecutionTimeInMillis");

+  }

+  @Test

+  public void testInstanceHasPfloInputField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("pfloInput");

+  }

+  @Test

+  public void testInstanceHasInternalTestDataField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("internalTestData");

+  }

+  @Test

+  public void testInstanceHasSimulationVthInputField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("simulationVthInput");

+  }

+  @Test

+  public void testInstanceHasTestDataField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("testData");

+  }

+  @Test

+  public void testInstanceHasVthInputField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("vthInput");

+  }

+  @Test

+  public void testInstanceHasCreatedAtField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("createdAt");

+  }

+  @Test

+  public void testInstanceHasUpdatedAtField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("updatedAt");

+  }

+  @Test

+  public void testInstanceHasCreatedByField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("createdBy");

+  }

+  @Test

+  public void testInstanceHasUpdatedByField(){

+    Assertions.assertThat(testInstance).hasFieldOrProperty("updatedBy");

+  }

+}

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
index 0000000..49fb31c
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/UserTest.java
@@ -0,0 +1,63 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models;

+

+import org.oran.otf.common.model.User;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class UserTest {

+

+  private static User user;

+  @BeforeClass

+  public static void setup(){

+    user = new User();

+  }

+  @Test

+  public void testUserHasPermissionsField(){

+    Assertions.assertThat(user).hasFieldOrProperty("permissions");

+  }

+  @Test

+  public void testUserHasFirstNameField(){

+    Assertions.assertThat(user).hasFieldOrProperty("firstName");

+  }

+  @Test

+  public void testUserHasLastNameField(){

+    Assertions.assertThat(user).hasFieldOrProperty("lastName");

+  }

+  @Test

+  public void testUserHasEmailField(){

+    Assertions.assertThat(user).hasFieldOrProperty("email");

+  }

+  @Test

+  public void testUserHasPasswordField(){

+    Assertions.assertThat(user).hasFieldOrProperty("password");

+  }

+  @Test

+  public void testUserHasGroupsField(){

+    Assertions.assertThat(user).hasFieldOrProperty("groups");

+  }

+  @Test

+  public void testUserHasCreatedAtField(){

+    Assertions.assertThat(user).hasFieldOrProperty("createdAt");

+  }

+  @Test

+  public void testUserHasUpdatedAtField(){

+    Assertions.assertThat(user).hasFieldOrProperty("updatedAt");

+  }

+}

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
index 0000000..2f7f166
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/BpmnTest.java
@@ -0,0 +1,83 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.BpmnInstance;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class BpmnTest {

+  private static BpmnInstance bpmnInstance;

+  @BeforeClass

+  public static void setup(){

+    bpmnInstance = new BpmnInstance();

+  }

+  @Test

+  public  void testBpmnInstanceHasProcessDefinitionIdField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("processDefinitionId");

+  }

+  @Test

+  public   void testBpmnInstanceHasDeploymentIdField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("deploymentId");

+  }

+  @Test

+  public  void testBpmnInstanceHasVersionField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("version");

+  }

+  @Test

+  public  void testBpmnInstanceHasBpmnFileIdField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("bpmnFileId");

+  }

+  @Test

+  public  void testBpmnInstanceHasResourceFileIdField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("resourceFileId");

+  }

+  @Test

+  public  void testBpmnInstanceHasIsDeployedField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("isDeployed");

+  }

+  @Test

+  public  void testBpmnInstanceHasTestHeadsField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("testHeads");

+  }

+  @Test

+  public  void testBpmnInstanceHasPflowsField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("pflos");

+  }

+  @Test

+  public  void testBpmnInstanceHasTestDataTemplateField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("testDataTemplate");

+  }

+  @Test

+  public  void testBpmnInstanceHasCreatedAtField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("createdAt");

+  }

+  @Test

+  public  void testBpmnInstanceUpdatedAtField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("updatedAt");

+  }

+  @Test

+  public  void testBpmnInstanceHasCreatedByField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("createdBy");

+  }

+  @Test

+  public  void testBpmnInstanceHasUpdatedByField(){

+    Assertions.assertThat(bpmnInstance).hasFieldOrProperty("updatedBy");

+  }

+

+}

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
index 0000000..b41c4ee
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/DeployTestStrategyRequestTest.java
@@ -0,0 +1,44 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.DeployTestStrategyRequest;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class DeployTestStrategyRequestTest {

+  private static DeployTestStrategyRequest deployTestStrategyRequest;

+

+  @BeforeClass

+  public static void setup(){

+    deployTestStrategyRequest = new DeployTestStrategyRequest();

+  }

+  @Test

+  public void testDeployTestStrategyRequestHasTestDefinitionDeployerIdField(){

+    Assertions.assertThat(deployTestStrategyRequest).hasFieldOrProperty("testDefinitionDeployerId");

+  }

+  @Test

+  public void testDeployTestStrategyRequestHasTestDefinitionIdField(){

+    Assertions.assertThat(deployTestStrategyRequest).hasFieldOrProperty("TestDefinitionId");

+  }

+  @Test

+  public void testDeployTestStrategyRequestHasDefinitionIdField(){

+    Assertions.assertThat(deployTestStrategyRequest).hasFieldOrProperty("DefinitionId");

+  }

+

+}

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
index 0000000..fe0ce0f
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/HeadNodeTest.java
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.TestHeadNode;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class HeadNodeTest {

+  private static TestHeadNode testHeadNode;

+

+  @BeforeClass

+  public static void setup(){

+    testHeadNode = new TestHeadNode();

+  }

+  @Test

+  public void testHeadNodeHasTestHeadIdField(){

+    Assertions.assertThat(testHeadNode).hasFieldOrProperty("testHeadId");

+  }

+  @Test

+  public void testHeadNodeHasBpmnVthTaskIdField(){

+    Assertions.assertThat(testHeadNode).hasFieldOrProperty("bpmnVthTaskId");

+  }

+

+}

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
index 0000000..1fde494
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/HeadResultTest.java
@@ -0,0 +1,60 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.TestHeadResult;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class HeadResultTest {

+  private static TestHeadResult testHeadResult;

+

+  @BeforeClass

+  public static void setup(){

+    testHeadResult = new TestHeadResult();

+  }

+  @Test

+  public void testHeadResultHasTestHeadIdField(){

+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("testHeadId");

+  }

+  @Test

+  public void testHeadResultHasTestHeadNameField(){

+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("testHeadName");

+  }

+  @Test

+  public void testHeadResultHasBpmnVthTaskIdField(){

+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("bpmnVthTaskId");

+  }

+  @Test

+  public void testHeadResultHasTestHeadRequestField(){

+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("testHeadRequest");

+  }

+  @Test

+  public void testHeadResultHasTestHeadResponseField(){

+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("testHeadResponse");

+  }

+  @Test

+  public void testHeadResultHasStartTimeField(){

+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("startTime");

+  }

+  @Test

+  public void testHeadResultHasEndTimeField(){

+    Assertions.assertThat(testHeadResult).hasFieldOrProperty("endTime");

+  }

+

+}

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
index 0000000..83fdf09
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/InstanceCreateRequestTest.java
@@ -0,0 +1,93 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.TestInstanceCreateRequest;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class InstanceCreateRequestTest {

+  private static TestInstanceCreateRequest testInstanceCreateRequest;

+

+  @BeforeClass

+  public static void setup() throws Exception{

+    //No Argument Constructor does not work because of the requiered name when creating

+    testInstanceCreateRequest = new TestInstanceCreateRequest(

+        "Name",

+        "Description",

+        null,

+        null,

+        null,

+        null,

+        null,

+        true,

+        false,

+        0L

+    );

+  }

+

+  @Test

+  public void testInstanceCreateRequestHasTestDefinitionIdField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("testDefinitionId");

+  }

+  @Test

+  public void testInstanceCreateRequestHasVersionField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("version");

+  }

+  @Test

+  public void testInstanceCreateRequestHasProcessDefinitionKeyField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("processDefinitionKey");

+  }

+  @Test

+  public void testInstanceCreateRequestHastestInstanceNameField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("testInstanceName");

+  }

+  @Test

+  public void testInstanceCreateRequestHasPfloInputField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("pfloInput");

+  }

+  @Test

+  public void testInstanceCreateRequestHasSimulationVthInputField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("simulationVthInput");

+  }

+  @Test

+  public void testInstanceCreateRequestHasTestDataField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("testData");

+  }

+  @Test

+  public void testInstanceCreateRequestHasVthInputField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("vthInput");

+  }

+  @Test

+  public void testInstanceCreateRequestHasCreatedByField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("createdBy");

+  }

+  @Test

+  public void testInstanceCreateRequestHasUseLatestTestDefinitionField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("useLatestTestDefinition");

+  }

+  @Test

+  public void testInstanceCreateRequestHasSimulationModeField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("simulationMode");

+  }

+  @Test

+  public void testInstanceCreateRequestHasMaxExecutionTimeInMillisField(){

+    Assertions.assertThat(testInstanceCreateRequest).hasFieldOrProperty("maxExecutionTimeInMillis");

+  }

+

+}

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
index 0000000..45c10a4
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/OtfApiResponseTest.java
@@ -0,0 +1,43 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.OTFApiResponse;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class OtfApiResponseTest {

+  private static OTFApiResponse otfApiResponse;

+  @BeforeClass

+  public static void setup(){

+    otfApiResponse = new OTFApiResponse();

+  }

+  @Test

+  public void testOtfApiResponseHasStatusCodeField(){

+    Assertions.assertThat(otfApiResponse).hasFieldOrProperty("statusCode");

+  }

+  @Test

+  public void testOtfApiResponseHasMessageField(){

+    Assertions.assertThat(otfApiResponse).hasFieldOrProperty("message");

+  }

+  @Test

+  public void testOtfApiResponseHasTimeField(){

+    Assertions.assertThat(otfApiResponse).hasFieldOrProperty("time");

+  }

+

+}

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
index 0000000..320df54
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/ParallelFlowInputTest.java
@@ -0,0 +1,47 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.ParallelFlowInput;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class ParallelFlowInputTest {

+  private static ParallelFlowInput parallelFlowInput;

+  @BeforeClass

+  public static void setup(){

+    parallelFlowInput = new ParallelFlowInput();

+  }

+  @Test

+  public void testParallelFlowInputHasArgsField(){

+    Assertions.assertThat(parallelFlowInput).hasFieldOrProperty("args");

+  }

+  @Test

+  public void testParallelFlowInputHasInterruptOnFailureField(){

+    Assertions.assertThat(parallelFlowInput).hasFieldOrProperty("interruptOnFailure");

+  }

+  @Test

+  public void testParallelFlowInputHasMaxFailuresField(){

+    Assertions.assertThat(parallelFlowInput).hasFieldOrProperty("maxFailures");

+  }

+  @Test

+  public void testParallelFlowInputHasThreadPoolSizeField(){

+    Assertions.assertThat(parallelFlowInput).hasFieldOrProperty("threadPoolSize");

+  }

+

+}

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
index 0000000..7a0a20b
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/PfloNodeTest.java
@@ -0,0 +1,40 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.PfloNode;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class PfloNodeTest {

+  private static PfloNode pfloNode;

+

+  @BeforeClass

+  public static void setup(){

+    pfloNode = new PfloNode();

+  }

+  @Test

+  public void testPfloNodeHasBpmnPfloTaskIdField(){

+    Assertions.assertThat(pfloNode).hasFieldOrProperty("bpmnPlfoTaskId");

+  }

+  @Test

+  public void testPfloNodeHasLabelField(){

+    Assertions.assertThat(pfloNode).hasFieldOrProperty("label");

+  }

+

+}

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
index 0000000..92e7497
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/UserGroupTest.java
@@ -0,0 +1,42 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.UserGroup;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class UserGroupTest {

+  private static UserGroup userGroup;

+

+  //TODO (DONE): Added NoArg Constructor to UserGroup model for testing

+  @BeforeClass

+  public static void setup(){

+    userGroup = new UserGroup();

+  }

+

+  @Test

+  public void testUserGroupHasGroupIdField(){

+    Assertions.assertThat(userGroup).hasFieldOrProperty("groupId");

+  }

+  @Test

+  public void testUserGroupHasPermissionsField(){

+    Assertions.assertThat(userGroup).hasFieldOrProperty("permissions");

+  }

+

+}

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
index 0000000..6debf07
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/models/local/WorkFlowRequestTest.java
@@ -0,0 +1,61 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.models.local;

+

+import org.oran.otf.common.model.local.WorkflowRequest;

+import org.assertj.core.api.Assertions;

+import org.junit.BeforeClass;

+import org.junit.Test;

+

+public class WorkFlowRequestTest {

+  private static WorkflowRequest workflowRequest;

+

+  @BeforeClass

+  public static void setup()throws Exception{

+    workflowRequest = new WorkflowRequest();

+  }

+

+  @Test

+  public void testWorkFlowRequestHasAsyncField(){

+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("async");

+  }

+  @Test

+  public void testWorkFlowRequestHasExecutorIdField(){

+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("executorId");

+  }

+  @Test

+  public void testWorkFlowRequestHasTestInstanceIdField(){

+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("testInstanceId");

+  }

+  @Test

+  public void testWorkFlowRequestHasPfloInputField(){

+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("pfloInput");

+  }

+  @Test

+  public void testWorkFlowRequestHasTestDataField(){

+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("testData");

+  }

+  @Test

+  public void testWorkFlowRequestHasVthInputField(){

+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("vthInput");

+  }

+  @Test

+  public void testWorkFlowRequestHasMaxExecutionTimeInMillisField(){

+    Assertions.assertThat(workflowRequest).hasFieldOrProperty("maxExecutionTimeInMillis");

+  }

+

+}

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
index 0000000..15afaa3
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/utility/BuildResponseTest.java
@@ -0,0 +1,74 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.utility;

+

+import org.oran.otf.api.Utilities;

+import org.oran.otf.common.model.local.OTFApiResponse;

+import org.junit.Assert;

+import org.junit.Test;

+

+import javax.ws.rs.core.Response;

+

+public class BuildResponseTest {

+    @Test

+    public void badResponseTest(){

+        Response badResponse = Utilities.Http.BuildResponse.badRequest();

+        Assert.assertNotNull(badResponse);

+        Assert.assertEquals(badResponse.getStatus(),400);

+    }

+

+    @Test

+    public void badRequestWithMessageTest() {

+        Response badResponse = Utilities.Http.BuildResponse.badRequestWithMessage("this is bad");

+        OTFApiResponse response = (OTFApiResponse) badResponse.getEntity();

+

+        Assert.assertNotNull(badResponse);

+        Assert.assertEquals(badResponse.getStatus(),400);

+        Assert.assertEquals(response.getStatusCode(), 400);

+        Assert.assertEquals(response.getMessage(), "this is bad");

+    }

+    @Test

+    public void internalServerErrorTest(){

+        Response badResponse = Utilities.Http.BuildResponse.internalServerError();

+        Assert.assertNotNull(badResponse);

+        Assert.assertEquals(badResponse.getStatus(),500);

+    }

+    @Test

+    public void internalServerErrorWithMessageTest(){

+        Response badResponse = Utilities.Http.BuildResponse.internalServerErrorWithMessage("internal error");

+        OTFApiResponse response = (OTFApiResponse) badResponse.getEntity();

+

+        Assert.assertNotNull(badResponse);

+        Assert.assertEquals(badResponse.getStatus(),500);

+        Assert.assertEquals(response.getStatusCode(), 500);

+        Assert.assertEquals(response.getMessage(), "internal error");

+    }

+

+    @Test

+    public void unauthorizedTest(){

+        Response basicUnauthorizedResponse=  Utilities.Http.BuildResponse.unauthorized();

+        Response unauthorizedMsgResponse = Utilities.Http.BuildResponse.unauthorizedWithMessage("unauthorized");

+        OTFApiResponse response = (OTFApiResponse) unauthorizedMsgResponse.getEntity();

+

+        Assert.assertNotNull(basicUnauthorizedResponse);

+        Assert.assertNotNull(unauthorizedMsgResponse);

+        Assert.assertEquals(basicUnauthorizedResponse.getStatus(),401);

+        Assert.assertEquals(unauthorizedMsgResponse.getStatus(),401);

+        Assert.assertEquals(response.getStatusCode(),401);

+        Assert.assertEquals(response.getMessage(),"unauthorized");

+    }

+}

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
index 0000000..e2d7954
--- /dev/null
+++ b/otf-service-api/src/test/java/org/oran/otf/api/tests/unit/utility/UserPermissionTest.java
@@ -0,0 +1,67 @@
+/*  Copyright (c) 2019 AT&T Intellectual Property.                             #

+#                                                                              #

+#   Licensed under the Apache License, Version 2.0 (the "License");            #

+#   you may not use this file except in compliance with the License.           #

+#   You may obtain a copy of the License at                                    #

+#                                                                              #

+#       http://www.apache.org/licenses/LICENSE-2.0                             #

+#                                                                              #

+#   Unless required by applicable law or agreed to in writing, software        #

+#   distributed under the License is distributed on an "AS IS" BASIS,          #

+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #

+#   See the License for the specific language governing permissions and        #

+#   limitations under the License.                                             #

+##############################################################################*/

+

+

+package org.oran.otf.api.tests.unit.utility;

+

+import org.oran.otf.common.utility.permissions.UserPermission;

+import org.junit.Assert;

+import org.junit.Before;

+import org.junit.Test;

+import org.junit.runner.RunWith;

+import org.mockito.InjectMocks;

+import org.mockito.Mock;

+import org.mockito.Mockito;

+import org.mockito.junit.MockitoJUnitRunner;

+

+import java.util.*;

+

+@RunWith(MockitoJUnitRunner.class)

+public class UserPermissionTest {

+

+    @Mock

+    Map<String, Set<String>> userAccessMap ;

+

+    @InjectMocks

+    private UserPermission userPermission;

+

+    @Before

+    public void setUp()

+    {

+        String fakeGroupId1 = "abc123";

+        Set<String> user1Permissions = new HashSet<>(Arrays.asList("READ","WRITE"));

+        Mockito.when(userAccessMap.get(fakeGroupId1)).thenReturn(user1Permissions);

+    }

+

+    @Test

+    public void testHasAccessToMethod(){

+

+        Assert.assertNotNull(userPermission.getUserAccessMap());

+        //test when user have access to group with certain permissions and a fake permission(mix of upper and lower case

+        Assert.assertTrue(userPermission.hasAccessTo("abc123","READ"));

+        Assert.assertTrue(userPermission.hasAccessTo("abc123","WrIte"));

+        Assert.assertFalse(userPermission.hasAccessTo("abc123","DEleTE"));

+        Assert.assertFalse(userPermission.hasAccessTo("abc123","ExECUTe"));

+        Assert.assertFalse(userPermission.hasAccessTo("abc123","mANAgEMENT"));

+        Assert.assertFalse(userPermission.hasAccessTo("abc123","READ+WRITE"));

+

+        //test when user have no access to the group

+        Assert.assertFalse(userPermission.hasAccessTo("edf567","READ"));

+        Assert.assertFalse(userPermission.hasAccessTo("edf567","WRITE"));

+        Assert.assertFalse(userPermission.hasAccessTo("edf567","DELETE"));

+        Assert.assertFalse(userPermission.hasAccessTo("edf567","EXECUTE"));

+        Assert.assertFalse(userPermission.hasAccessTo("edf567","MANAGEMENT"));

+    }

+}

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
index 0000000..a0a7d2a
--- /dev/null
+++ b/otf-service-api/src/test/resources/application-test.properties
@@ -0,0 +1,19 @@
+server.port=8443

+server.port.http=8181

+

+otf.mongo.hosts=${OTF_MONGO_HOSTS}

+otf.mongo.username=${OTF_MONGO_USERNAME}

+otf.mongo.password=${OTF_MONGO_PASSWORD}

+otf.mongo.replicaSet=${OTF_MONGO_REPLICASET}

+otf.mongo.database=${OTF_MONGO_DATABASE}

+

+cadi.prop.files=src/main/resources/cadi.properties

+

+otf.proxy=localhost

+otf.proxy-port=8080

+otf.embedded.host=localhost

+otf.embedded.port=5555

+otf.embedded.database=otf

+

+otf.mechid=${AAF_ID}

+otf.mechpass=${AAF_MECH_PASSWORD}

diff --git a/otf-service-api/swagger.json b/otf-service-api/swagger.json
new file mode 100644
index 0000000..2f5f6c8
--- /dev/null
+++ b/otf-service-api/swagger.json
@@ -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
index 0000000..7bae19f
--- /dev/null
+++ b/otf-service-api/swagger.yml
@@ -0,0 +1,714 @@
+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/OtfApiResponse'

+  /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

+            The 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/OtfApiResponse'

+  /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: {}

+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 model2 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.

+            See 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.

+            See 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 model2 for a test instance creation request.

diff --git a/otf-ssh-test-head/Dockerfile b/otf-ssh-test-head/Dockerfile
index 84e31c1..6737bd3 100644
--- a/otf-ssh-test-head/Dockerfile
+++ b/otf-ssh-test-head/Dockerfile
@@ -1,9 +1,13 @@
 FROM python:2.7

 

-ARG HTTP_PROXY="localhost:8080"

-ARG HTTPS_PROXY="localhost:8080"

-ARG http_proxy="localhost:8080"

-ARG https_proxy="localhost:8080"

+# ARG HTTP_PROXY="localhost:8080"

+# ARG HTTPS_PROXY="localhost:8080"

+# ARG http_proxy="localhost:8080"

+# ARG https_proxy="localhost:8080"

+

+ENV NAMESPACE=namespace

+ENV APP_NAME=otf-ping-test-head

+ENV APP_VERSION=1.0

 

 RUN python --version