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> {{ 'Dashboard' | translate }}
+ </a>
+
+ <a [routerLink]="['/test-definitions']" [routerLinkActive]="['router-link-active']" class="list-group-item">
+ <i class="fa fa-fw fa-object-group"></i> {{ '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> {{ '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> {{ 'Collection' | translate }}
+ </a>
+ </li>
+ <li>
+ <a [routerLink]="['/modeler']" [routerLinkActive]="['router-link-active']" class="list-group-item">
+ <i class="fa bpmn-icon-bpmn-io"></i> {{ '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> {{ 'Test Instances' | translate }}
+ </a>
+ <!--<a [routerLink]="['/test-executions']" [routerLinkActive]="['router-link-active']" class="list-group-item">
+ <i class="fa fa-fw fa-bolt"></i> {{ 'Test Executions' | translate }}
+ </a>
+ <a routerLink="/onboarding" [routerLinkActive]="['router-link-active']" class="list-group-item">
+ <i class="fa fa-fw fa-user"></i> {{ 'Onboarding' | translate }}
+ </a>-->
+ <!--<div class="nested-menu">
+ <a class="list-group-item" (click)="addExpandClass('pages1')">
+ <span><i class="fa fa-fw fa-user"></i> {{ '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> {{ '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> {{ '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> {{ '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> {{ '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> {{ '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> {{ '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> {{ '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> {{ '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> {{ 'Scheduling' | translate }}
+ </a>-->
+ <a [routerLink]="['/modeler']" [routerLinkActive]="['router-link-active']" class="list-group-item">
+ <i class="fa bpmn-icon-bpmn-io"></i> 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> {{ 'Manage Group' | translate }}
+ </a>
+ <a [routerLink]="['/feedback']" [routerLinkActive]="['router-link-active']" class="list-group-item">
+ <i class="fa fa-fw fa-comment-o"></i> {{ '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> {{ '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> {{ '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> {{ '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> {{ '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> {{ '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 += ' ';
+ }
+ 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>
+
+ <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
+ | {{ 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 += ' ';
+ }
+ 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>
+ </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