From: Rohan Patel Date: Wed, 25 Sep 2019 18:35:28 +0000 (-0400) Subject: added svcapi ui and camunda code X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?p=it%2Fotf.git;a=commitdiff_plain;h=14f6f95c84a4a1fa8774190db4a03fd0214ec55f added svcapi ui and camunda code Signed-off-by: Rohan Patel 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 @@ + + + 4.0.0 + + org.oran.otf + otf-camunda + Camille.1.1 + jar + + + + com.github.tomakehurst + wiremock-jre8 + 2.24.0 + test + + + org.mockito + mockito-core + 2.15.0 + test + + + org.mockito + mockito-inline + test + + + io.rest-assured + rest-assured + 4.0.0 + test + + + io.rest-assured + rest-assured-all + 4.0.0 + test + + + + + + + org.camunda.bpm + camunda-engine + ${camunda.bpm.version} + + + org.camunda.bpm + camunda-engine-plugin-spin + ${camunda.bpm.base.version} + + + org.camunda.bpm + camunda-engine-plugin-connect + ${camunda.bpm.base.version} + + + org.camunda.connect + camunda-connect-connectors-all + + + org.camunda.bpm.springboot + camunda-bpm-spring-boot-starter + ${camunda.springboot.version} + + + org.camunda.bpm.springboot + camunda-bpm-spring-boot-starter-rest + ${camunda.springboot.version} + + + org.camunda.bpm.springboot + camunda-bpm-spring-boot-starter-webapp-ee + ${camunda.springboot.version} + + + camunda-external-task-client + org.camunda.bpm + ${camunda.bpm.external-task-client.version} + + + + + org.camunda.bpm.extension.mockito + camunda-bpm-mockito + ${camunda.mockito.version} + test + + + org.camunda.bpm.extension.reactor + camunda-bpm-reactor-core + ${camunda.bpm.reactor.version} + + + camunda-bpm-reactor-spring + org.camunda.bpm.extension.reactor + ${camunda.bpm.reactor.version} + + + org.camunda.bpm.extension + camunda-bpm-assert + ${camunda.bpm.assert.version} + + + + + + org.camunda.spin + camunda-spin-core + ${camunda.spin.version} + + + org.camunda.spin + camunda-spin-dataformat-all + ${camunda.spin.version} + + + + + org.springframework.boot + spring-boot-starter-actuator + ${springboot.version} + + + spring-boot-starter-amqp + org.springframework.boot + ${springboot.version} + + + org.springframework.boot + spring-boot-starter-jersey + ${springboot.version} + + + org.springframework.boot + spring-boot-starter-data-mongodb + ${springboot.version} + + + org.springframework.boot + spring-boot-starter-test + test + ${springboot.version} + + + + + + + + org.springframework.boot + spring-boot-configuration-processor + ${springboot.version} + true + + + org.springframework.boot + spring-boot-starter-json + ${springboot.version} + + + org.springframework.boot + spring-boot-starter-jdbc + ${springboot.version} + + + + + org.onap.aaf.authz + aaf-auth-client + ${cadi.version} + + + org.onap.aaf.authz + aaf-cadi-core + ${cadi.version} + + + org.onap.aaf.authz + aaf-cadi-aaf + ${cadi.version} + + + + com.h2database + h2 + + + org.apache.commons + commons-lang3 + 3.4 + + + org.codehaus.groovy + groovy-all + ${groovy.version} + + + com.google.code.gson + gson + ${google.gson.version} + + + com.google.guava + guava + ${google.guava.version} + + + org.python + jython-standalone + ${python.version} + + + + mysql + mysql-connector-java + 8.0.14 + + + de.flapdoodle.embed.mongo + de.flapdoodle.embed + test + + + jersey-media-multipart + org.glassfish.jersey.media + + + junit + junit + + + httpclient + org.apache.httpcomponents + 4.5.7 + + + httpasyncclient + org.apache.httpcomponents + 4.1.4 + + + jackson-module-kotlin + com.fasterxml.jackson.module + ${jackson.version} + + + jackson-datatype-jsr310 + com.fasterxml.jackson.datatype + ${jackson.version} + + + org.apache.commons + commons-vfs2 + 2.2 + + + com.jcraft + jsch + 0.1.54 + + + + org.springframework.boot + spring-boot-starter-parent + 2.1.4.RELEASE + + + + + + org.camunda.bpm + camunda-bom + ${camunda.version} + import + pom + + + + + + + + false + ${skipTests} + ${skipTests} + + 2.1.10 + registry.hub.docker.io + 7.10.0-ee + 2.0-alpha2 + 7.10.0 + 1.1.0 + 2.1.2 + 7.10.4-ee + 1.1.1 + 3.2.1 + 1.6.6 + 3.2.0 + 27.1-jre + 2.8.5 + 2.1.3 + 2.9.5 + 2.7.1 + 2.1.4.RELEASE + + + otf-camunda + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${springboot.version} + + + + org.python + jython-standalone + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + ${skipUTs} + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.22.1 + + + run-integration-tests + integration-test + + verify + + + + + ${skipTests} + ${skipITs} + + + + + + + src/main/resources + ${basedir}/target/src/main/resources + true + + **/* + + + otf_dev.p12 + + + + src/main/resources + true + + **/* + + + otf_dev.p12 + + + + src/main/resources + ${basedir}/target/src/main/resources + + otf_dev.p12 + + + + src/main/resources + + otf_dev.p12 + + + + docker + ${basedir}/target + + Dockerfile + + + + + otf-camunda + One of the core components of the Open Test Framework Test Control Unit. + + + \ 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 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 cadiFilterRegistration() { + FilterRegistrationBean 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 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 otfApiEnforcementFilterRegistration() + throws ServletException { + FilterRegistrationBean 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 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> 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 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 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 { + + @Override + public Class 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 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 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; + + 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 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 variables) + throws Exception { + final String logPrefix = Utility.getLoggerPrefix(); + logger.info(logPrefix + "::execute()"); + + // Get vthInput from the Camunda execution variable map. + List> 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 headers = new HashMap<>(); + String method = ""; + Map payload = new HashMap<>(); + int timeoutInMillis = 0; + + if (oHeaders != null) { + try { + headers = (Map) 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) 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 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 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 internalTestDataByActivity = (Map) testInstance.getInternalTestData().get(currentActivityId); + String robotFileId = (String) internalTestDataByActivity.get("robotFileId"); + Map testData = new HashMap<>(); + Map vthInput = new HashMap<>(); + testData.put("robotFileId", robotFileId); + vthInput.put("testData", testData); + Map payload = (Map) 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 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://:3905/events/"; + + @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 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 dmaapRequests = null; + try { + dmaapRequests = + Convert.listToObjectList(testDataActivity, new TypeReference>() {}); + } 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 testDetails = utility.getTestDetails(execution.getVariables(), logPrefix); + + // Post results to Dmaap + Map 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 postResultsToDmaap( + TestExecution execution, List dmaapRequests, String logPrefix) { + String payload = execution.toString(); + Map results = new HashMap<>(); + Map 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("", request.getHostname()); + url = url.replace("", 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 getResponse( + String url, String payload, Map headers, boolean proxy) + throws Exception { + HttpResponse response = RequestUtility.postSync(url, payload, headers, proxy); + String sRes = EntityUtils.toString(response.getEntity()); + Map 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 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 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 pfloInput = + (Map) 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 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 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 variables = execution.getVariables(); + Map testData = utility.getTestData(variables, logPrefix); + + Map sendMailData = null; + try { + sendMailData = + (Map) 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 recipients = null; + try { + recipients = (ArrayList) (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 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 { + + 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 { + + 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 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 { + + 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 headers; + private final Map 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 headers, + Map 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 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 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 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 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 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 simulateVTH(String activityId, Map simulationVth) { + int delay = 0; + Map response = new HashMap(); + 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() { + @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 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 getVariables() { + return variables; + } + + public void setVariables(Map 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 gracefulShutdown(){ + Set 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 processIds){ + RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine( + OtfCamundaConfiguration.processEngineName).getRuntimeService(); + for(String id: processIds){ + runtimeService.suspendProcessInstanceById(id); + } + } + + private Set queryProcessInstances(Set 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 del = new ArrayList<>(processIds); + runtimeService.deleteProcessInstances(del, "Camunda Shutting down, proccess forcefully terminated", false, false , false); + return processIds; + + } + + private Set shutdownAllProcessThreads(Set processIds){ + Set terminatedProcesses = new HashSet<>(); + Iterator processes = processIds.iterator(); + //Iterator processes = WorkflowTask.workflowTasksByExecutionId.entrySet().iterator(); + while(processes.hasNext()){ + Object processHolder = processes.next(); + List tasks = WorkflowTask.workflowTasksByExecutionId.get(processHolder.toString()); + //List 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 processIds){ + //Set processInstanceIds = this.runningProcessInstanceIds(); + Iterator 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 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 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>> set = + WorkflowTask.workflowTasksByExecutionId.entrySet(); + + for (Entry> entry : set) { + String processInstanceId = entry.getKey(); + List 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 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 inputVariables) { + return getOrCreate(inputVariables, "otf-business-key"); + } + + protected static Map getInputVariables(VariableMapImpl variableMap) { + Map inputVariables = new HashMap<>(); + @SuppressWarnings("unchecked") + Map vMap = (Map) variableMap.get("variables"); + for (Map.Entry entry : vMap.entrySet()) { + String vName = entry.getKey(); + Object value = entry.getValue(); + @SuppressWarnings("unchecked") + Map valueMap = (Map) value; // value, type + inputVariables.put(vName, valueMap.get("value")); + } + return inputVariables; + } + + protected static String getOrCreate(Map 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 variableValueType) { + Map 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 vthInput = + request.getVthInput() == null ? testInstance.getVthInput() : request.getVthInput(); + Map testData = + request.getTestData() == null ? testInstance.getTestData() : request.getTestData(); + Map plfoInput = + request.getPfloInput() == null ? testInstance.getPfloInput() : request.getPfloInput(); + + testInstance.setVthInput((HashMap) vthInput); + testInstance.setTestData((HashMap) testData); + testInstance.setPfloInput((HashMap) 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 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 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 generateTestDetailsWithMessage(String message) { + Map 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 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 pfloInput = null; + private Map testData = null; + private Map 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 pfloInput, + Map testData, + Map 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 pfloInput, + @JsonProperty(value = "testData", required = false) Map testData, + @JsonProperty(value = "vthInput", required = false) Map 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 getPfloInput() { + return pfloInput; + } + + public void setPfloInput(Map pfloInput) { + this.pfloInput = pfloInput; + } + + public Map getTestData() { + return testData; + } + + public void setTestData(Map testData) { + this.testData = testData; + } + + public Map getVthInput() { + return vthInput; + } + + public void setVthInput(Map 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 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 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> 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> 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 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> getFutures() { + return futures; + } + + public void setFutures(List> futures) { + this.futures = futures; + } + + public static void printWorkflowTaskResources() { + for (Map.Entry> entry : workflowTasksByExecutionId.entrySet()) { + logger.info( + "{}--------------Parent processInstanceId: {}--------------", logPrefix, entry.getKey()); + + List workflowTasks = + workflowTasksByExecutionId.getOrDefault(entry.getKey(), null); + for (WorkflowTask task : workflowTasks) { + task.print(); + } + } + } + + public static void printThreadInformation() { + Set 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 getExecutionVariable(Map variables, String key, Class 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 map = (Map) 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 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 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 getTestData(Map variables, String logPrefix) + throws TestExecutionException { + // Get vthInput from the Camunda execution variable map. + @SuppressWarnings({"unchecked"}) + Map testData = + (Map) + 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 variables, String currentActivityId, String logPrefix) + throws TestExecutionException, NullPointerException { + // Get vthInput from the Camunda execution variable map. + @SuppressWarnings({"unchecked"}) + Map testData = + (Map) + 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 getPfloInputByActivity( + Map variables, String currentActivityId, String logPrefix) + throws TestExecutionException, NullPointerException { + // Get vthInput from the Camunda execution variable map. + @SuppressWarnings({"unchecked"}) + Map pfloInput = + (Map) + 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 activityParameters = + (Map) 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> getVthInput( + Map variables, String currentActivityId, String logPrefix) + throws TestExecutionException, NullPointerException, IllegalArgumentException { + // Get vthInput from the Camunda execution variable map. + @SuppressWarnings({"unchecked"}) + Map vthInput = + (Map) + 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> lActivityParameters; + // Legacy hack + try { + @SuppressWarnings("unchecked") + Map mActivityParameters = new HashMap<>(); + mActivityParameters.put("method", "post"); + mActivityParameters.put("payload", Utility.toMap(oActivityParameters)); + Map 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>) 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 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 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 getTestDetails(Map 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 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 testDetails = + (Map) + 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 mechanizedIds; + private ObjectId ownerId; + private List roles; + private List 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 getMechanizedIds() { + return mechanizedIds; + } + + public void setMechanizedIds(List mechanizedIds) { + this.mechanizedIds = mechanizedIds; + } + + public ObjectId getOwnerId() { + return ownerId; + } + + public void setOwnerId(ObjectId ownerId) { + this.ownerId = ownerId; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public List getMembers() { + return members; + } + + public void setMembers(List 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 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 getRoles() { + return roles; + } + + public void setRoles(List 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 permissions; + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List 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 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 getBpmnInstances() { + return bpmnInstances; + } + + public void setBpmnInstances(List 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 testDetails; + private List testHeadResults; + private List 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 testDetails, + List testHeadResults, + List 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 getTestDetails() { + return testDetails; + } + + public void setTestDetails(Map testDetails) { + this.testDetails = testDetails; + } + + public List getTestHeadResults() { + synchronized (testHeadResults) { + return testHeadResults; + } + } + + public void setTestHeadResults(List testHeadResults) { + synchronized (testHeadResults) { + this.testHeadResults = testHeadResults; + } + } + + public List getTestInstanceResults() { + synchronized (testInstanceResults) { + return testInstanceResults; + } + } + + public void setTestInstanceResults(List 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 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 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 getVthInputTemplate() { + return vthInputTemplate; + } + + public void setVthInputTemplate(Map 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 pfloInput; + private Map internalTestData; + private Map simulationVthInput; + private Map testData; + private Map 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 pfloInput, + HashMap internalTestData, + HashMap simulationVthInput, + HashMap testData, + HashMap 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 getPfloInput() { + return pfloInput; + } + + public void setPfloInput(HashMap pfloInput) { + this.pfloInput = pfloInput; + } + + public Map getInternalTestData() { + return internalTestData; + } + + public void setInternalTestData(HashMap internalTestData) { + this.internalTestData = internalTestData; + } + + public Map getSimulationVthInput() { + return simulationVthInput; + } + + public void setSimulationVthInput(HashMap simulationVthInput) { + this.simulationVthInput = simulationVthInput; + } + + public Map getTestData() { + return testData; + } + + public void setTestData(HashMap testData) { + this.testData = testData; + } + + public Map getVthInput() { + return vthInput; + } + + public void setVthInput(HashMap 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 permissions; + private String firstName; + private String lastName; + private String email; + private String password; + private List groups; + private Date createdAt; + private Date updatedAt; + + public User( + ObjectId _id, + List permissions, + String firstName, + String lastName, + String email, + String password, + List 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 getPermissions() { + return permissions; + } + + public void setPermissions(List 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 getGroups() { + return groups; + } + + public void setGroups(List 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 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 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 getHistoricBpmnInstanceAsList( + List bpmnInstances, String processDefinitionId) { + BpmnInstance bpmnInstance = + bpmnInstances.stream() + .filter( + _bpmnInstance -> { + return _bpmnInstance.isDeployed() + && _bpmnInstance.getProcessDefinitionId() != null + && _bpmnInstance.getProcessDefinitionId().equals(processDefinitionId); + }) + .findFirst() + .orElse(null); + + List 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 getBpmnInstances() { + return bpmnInstances; + } + + public void setBpmnInstances(List 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 pfloInput; + private Map simulationVthInput; + private Map testData; + private Map 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 pfloInput, + HashMap simulationVthInput, + HashMap testData, + HashMap 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 getPfloInput() { + return pfloInput; + } + + public void setPfloInput( + HashMap pfloInput) { + this.pfloInput = pfloInput; + } + + public Map getSimulationVthInput() { + return simulationVthInput; + } + + public void setSimulationVthInput( + HashMap simulationVthInput) { + this.simulationVthInput = simulationVthInput; + } + + public Map getTestData() { + return testData; + } + + public void setTestData(HashMap testData) { + this.testData = testData; + } + + public Map getVthInput() { + return vthInput; + } + + public void setVthInput(HashMap 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 testHeads; + private List pflos; + private Map 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 testHeads, + List pflos, + Map 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 getTestHeads() { + return testHeads; + } + + public void setTestHeads(List testHeads) { + this.testHeads = testHeads; + } + + public List getPflos() { + return pflos; + } + + public void setPflos(List pflos) { + this.pflos = pflos; + } + + public Map getTestDataTemplate() { + return testDataTemplate; + } + + public void setTestDataTemplate(Map 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; + private List historicIncident; + private List> historicJobLog; + private List> historicExternalTaskLog; + private List> historicVariableInstance; + + public OTFProcessInstanceCompletionResponse() { + } + + + public HistoricProcessInstance getHistoricProcessInstance() { + return historicProcessInstance; + } + + public void setHistoricProcessInstance(HistoricProcessInstance historicProcessInstance) { + this.historicProcessInstance = historicProcessInstance; + } + + public List getHistoricActivityInstance() { + return historicActivityInstance; + } + + public void setHistoricActivityInstance(List historicActivityInstance) { + this.historicActivityInstance = historicActivityInstance; + } + + public List getHistoricIncident() { + return historicIncident; + } + + public void setHistoricIncident(List historicIncident) { + this.historicIncident = historicIncident; + } + + public List> getHistoricJobLog() { + return historicJobLog; + } + + public void setHistoricJobLog(List historicJobLog) { + List> list = new ArrayList>(); + 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> getHistoricExternalTaskLog() { + return this.historicExternalTaskLog; + } + + public void setHistoricExternalTaskLog(List historicExternalTaskLog) { + List> list = new ArrayList>(); + 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> getHistoricVariableInstance() { + return historicVariableInstance; + } + + public void setHistoricVariableInstance(List historicVariableInstance) { + List> list = new ArrayList>(); + 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 args; + private boolean interruptOnFailure; + private int maxFailures; + private int threadPoolSize; + + public ParallelFlowInput() { + } + + @JsonCreator + public ParallelFlowInput( + @JsonProperty(value = "args", required = true) List 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 getArgs() { + return args; + } + + public void setArgs(List 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 headers; + private Map body; + + public TestHeadRequest(){} + + public TestHeadRequest(Map headers, + Map body) { + this.headers = headers; + this.body = body; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Map getBody() { + return body; + } + + public void setBody(Map 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 testHeadHeaders; + //private int testHeadCode; + private int statusCode; + + private TestHeadRequest testHeadRequest; + private Map 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 to RequestContent + int statusCode, + + TestHeadRequest testHeadRequest, + Map 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 getTestHeadResponse() { + return testHeadResponse; + } + + public void setTestHeadResponse(Map 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 permissions; + + public UserGroup(ObjectId groupId, List permissions) { + this.groupId = groupId; + this.permissions = permissions; + } + + public ObjectId getGroupId() { + return groupId; + } + + public void setGroupId(ObjectId groupId) { + this.groupId = groupId; + } + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List 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 { + @Query("{ 'members.userId': ?0 }") + public List 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 { + Optional 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 { + Optional 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 { + +} 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 { + Optional findByTestInstanceName(String testInstanceName); + + @Query("{ 'testDefinitionId': ?0 }") + List findAllByTestDefinitionId(ObjectId testDefinitionId); + + @Query("{ 'testDefinitionId': ?0, 'processDefinitionId': ?1 }") + List 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 { + Optional 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 boolean identifierExistsInCollection( + MongoRepository repository, ObjectId identifier) { + return repository.findById(identifier.toString()).isPresent(); + } + + public static T findByIdGeneric(MongoRepository repository, ObjectId identifier) { + Optional 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() { + @Override + public JsonElement serialize( + ObjectId src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.toHexString()); + } + }) + .registerTypeAdapter( + ObjectId.class, + new JsonDeserializer() { + @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 jsonToMap(String json) { + Type type = new TypeToken>() { + }.getType(); + return getGson().fromJson(json, type); + } + + public static String objectToJson(Object obj) { + return getGson().toJson(obj); + } + + public static T jsonToObject(String json, TypeReference typeReference) throws IOException { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + return objectMapper.readValue(json, typeReference); + } + + public static T mapToObject(Map map, TypeReference typeReference) throws IOException { + return jsonToObject(mapToJson(map), typeReference); + } + + public static T listToObjectList(List list, TypeReference 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 maskAuth(Map headers){ + //Deep copy headers to avoid changing original + Gson gson = new Gson(); + String jsonString = gson.toJson(headers); + Map 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 headers, Boolean proxy) + throws Exception { + HttpPost post = buildPost(url, body, headers); + executeAsync(post, proxy); + } + + public static HttpResponse postSync( + String url, String body, Map headers, Boolean proxy) throws Exception { + HttpPost post = buildPost(url, body, headers); + return executeSync(post, proxy); + } + + public static HttpResponse postSync( + String url, String body, Map 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 headers, Boolean proxy) + throws Exception { + HttpGet get = buildGet(url, headers); + return executeSync(get, proxy); + } + + public static HttpResponse getSync( + String url, Map 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 headers, Boolean proxy) + throws Exception { + HttpGet get = buildGet(url, headers); + executeAsync(get, proxy); + } + + private static HttpPost buildPost(String url, String body, Map 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 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 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 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 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 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> 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 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 getUserGroupPermissions(User user, Group group){ + Set permissionsAllowed = new HashSet<>(); + Set 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 getRolePermissions(String userRoleName, Group group) + { + for(Role role : group.getRoles()) + { + if(role.getRoleName().equalsIgnoreCase(userRoleName)) + { + return new HashSet(role.getPermissions()); + } + } + return new HashSet(); // 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 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(member.getRoles()); + } + return new HashSet(); //if user have no roles + } + + // create map that where key is the group id and value = users permission (string) that that group + private Map> mapGroupsToPermission(User user, GroupRepository groupRepository){ + Map> groupAccessMap = new HashMap<>(); + List enrolledGroups = groupRepository.findAllByMembersId(user.get_id());// enrolledGroups = groups that user is a member of + Map allGroupMap = groupListToMap(groupRepository.findAll()); + // get all permission in the groups the user is ia member of + for(Group group: enrolledGroups) { + Set permissions = getUserGroupPermissions(user,group); + groupAccessMap.put(group.get_id().toString(),convertPermissions(permissions)); + } + //assign add read to all parent groups + Set 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(Arrays.asList(UserPermission.Permission.READ))); + } + // if there is management role + // then assign read access to children + if(hasManagementRole(user,enrolledGroups)){ +// SetchildIds = 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 childIds= getChildrenGroupsId(Arrays.asList(enrolledGroup),allGroupMap,user); + Set 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 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 getParentGroups(List enrolledGroup, Map groupMap ) + { + Set parentGroups = new HashSet<>(); + return lookUp(enrolledGroup,groupMap,parentGroups); + } + //recursive lookup starting at the enrolled groups that the user is a member of + private Set lookUp(List groupsToCheck, Map groupMap, Set 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 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 groupListToMap(List allGroups) + { + Map groupMap = new HashMap<>(); + allGroups.forEach(group -> groupMap.put(group.get_id().toString(),group)); + return groupMap; + } + //get all the child group + private Set getChildrenGroupsId(List enrolledGroup, Map allGroupsMap, User user) + { + Set childrenGroups = new HashSet<>(); + Set managementGroupIds = getManagementGroupIds(enrolledGroup,user); + return lookForChildren(managementGroupIds,allGroupsMap,childrenGroups); + } + + private Set getManagementGroupIds(List enrolledGroups, User user) + { + Set 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 lookForChildren (Set parentIds, Map allGroupsMap, Set resultSet) + { + //base case = no groups to check anymore; + if (parentIds.isEmpty()) + return resultSet; + + Set 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(SetparentGroupIds, 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 convertPermissions (Set permissions){ + Set 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> userAccessMap; + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Map> getUserAccessMap() { + return userAccessMap; + } + + public void setUserAccessMap(Map> userAccessMap) { + this.userAccessMap = userAccessMap; + } + + public boolean hasAccessTo(String groupId,String permission) { + if (userAccessMap.get(groupId) == null) { + return false; + } + Set 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 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 deleteProcessInstanceInternal( + String executionId, String deleteReason) { + RuntimeService runtimeService = BpmPlatform.getProcessEngineService().getProcessEngine(OtfCamundaConfiguration.processEngineName).getRuntimeService(); + + ProcessInstance pi = + runtimeService.createProcessInstanceQuery().processInstanceId(executionId).singleResult(); + + Map 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 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 testDefinitionQuery = + testDefinitionRepository.findById(testDefinitionId); + if (!testDefinitionQuery.isPresent()) { + return Response.status(404).build(); + } + TestDefinition testDefinition = testDefinitionQuery.get(); + + List bpmnInstances = testDefinition.getBpmnInstances(); + // List indices = new ArrayList(); + 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()); + // testDefinitionRepository.save(testDefinition); + return ResponseUtility.Build.okRequest(); + + + } + + + // delete all deployments +// public Response deleteAllTestStrategies() { +//// create a database to retrieve all process definitions +// List 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 response = new HashMap(); +// 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 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 = historyService + .createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list(); + + List historicIncident = + historyService.createHistoricIncidentQuery().processInstanceId(processInstanceId).list(); + + List historicJobLog = + historyService.createHistoricJobLogQuery().processInstanceId(processInstanceId).list(); + + List historicExternalTaskLog = + historyService.createHistoricExternalTaskLogQuery().processInstanceId(processInstanceId).list(); + + List 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() { + }); + + 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 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 applicationJersey() { + ServletRegistrationBean 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 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 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 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 @@ + + + + + + false + true + + + + \ 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 @@ + + + + + SequenceFlow_1gpkkbm + + + SequenceFlow_1psgifi + + + + + + + + SequenceFlow_1gpkkbm + SequenceFlow_12x2s0z + var vthInput = { + vthInput: { + Task_1r783jz: { + testData: { + targetHost: "8.8.8.8", + useJumpServer: false + }, + vthName: "Unused parameter", + testConfig: {} + } + } +}; + +execution.setVariable("vthInput", JSON.stringify(vthInput)); + + + SequenceFlow_12x2s0z + SequenceFlow_054puyx + + + SequenceFlow_054puyx + SequenceFlow_1psgifi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 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 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 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 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 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 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 @@ + + + +

+ access-denied works! +

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; + + 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 @@ + + + + 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; + + 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 @@ + + + + \ 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 + + 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, next: HttpHandler): Observable> { + 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 @@ + + + + + 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 + + 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; + 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; + }); + + 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 @@ + + + + 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; + + 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 @@ + + + + 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 + + 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(2).png b/otf-frontend/client/src/app/layout/components/stats/assets/icons/ProfessionalHeadshot(2).png new file mode 100644 index 0000000..6fc07ef Binary files /dev/null and b/otf-frontend/client/src/app/layout/components/stats/assets/icons/ProfessionalHeadshot(2).png differ diff --git a/otf-frontend/client/src/app/layout/components/stats/assets/icons/download.jfif b/otf-frontend/client/src/app/layout/components/stats/assets/icons/download.jfif new file mode 100644 index 0000000..2fad5ae Binary files /dev/null and b/otf-frontend/client/src/app/layout/components/stats/assets/icons/download.jfif differ diff --git a/otf-frontend/client/src/app/layout/components/stats/assets/icons/equalizer.gif b/otf-frontend/client/src/app/layout/components/stats/assets/icons/equalizer.gif new file mode 100644 index 0000000..22ccbe3 Binary files /dev/null and b/otf-frontend/client/src/app/layout/components/stats/assets/icons/equalizer.gif differ diff --git a/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.gif b/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.gif new file mode 100644 index 0000000..5d979f7 Binary files /dev/null and b/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.gif differ diff --git a/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.svg b/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.svg new file mode 100644 index 0000000..d34f7b3 --- /dev/null +++ b/otf-frontend/client/src/app/layout/components/stats/assets/icons/loading-pies.svg @@ -0,0 +1 @@ + \ 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; + + 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 = []; + public testInstances: Array = []; + //public scheduleInstances: Array = []; + //public vths = []; + + constructor( + public dialogRef: MatDialogRef, + 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; + + 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; + + 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 = []; + + @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; + + 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 { + 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; + + 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 = []; + + @ViewChild('pieChartDiv') PieChartDiv: ElementRef; + @ViewChild('legendDiv') legendDiv: ElementRef; + @Input() height: string; + + protected stats: StatsService; + public chart: am4charts.PieChart; + private chartData: Array; + 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; + 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; + + 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 { + 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, + selectedTestInstances?: Array +} + +@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 = []; + + 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 = new Subject(); + public tiExecutionsChange: Subject = new Subject(); + public scheduleChange: Subject = new Subject(); + public finishedDefaultData: Subject = new Subject(); + public startDefaultData: Subject = new Subject(); + public startTDExecutionCall: Subject = new Subject(); + public startTIExecutionCall: Subject = new Subject(); + public windowResized: Subject = new Subject(); + public startScheduleCall: Subject = new Subject(); + + 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; + 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; + 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; + let resultingData: Array = fetchedData; + if (schedFilters.selectedInstances.length !== 0) { + resultingData = fetchedData.filter(el => { + let fetchedID = el.data.testSchedule._testInstanceId; + let selectedIDs = schedFilters.selectedInstances as Array; + 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; + 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; + + 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 = []; + + @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; + + 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 = []; + + @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; + + // 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; + + 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 = []; + + @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; + + 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 + '' + key + ':
'; + if (json.constructor === Array && toInteger(key) > 0) { + tabs--; + } + html += this.json2html(json[key], ++tabs); + } else { + html += tabHtml + '' + key + ':' + '
'; + if (typeof json[key] === 'string') { + json[key] = json[key].replace(/\\n/g, '
' + tabHtml); + } + html += tabHtml + json[key] + '
'; + html += '
'; + } + } + } + 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 */ + 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; + + 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 = []; + + // 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; + + 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; + + 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 @@ + + + + + +
+ +
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; + + 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; + + 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, + 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; + + 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; + + 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 Binary files /dev/null and b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/cat.gif differ diff --git a/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/index.js b/otf-frontend/client/src/app/layout/modeler/custom-elements/log-test-result/image/index.js new file mode 100644 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; + + 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; + 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; + 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 = (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 @@ + + + + + SequenceFlow_1gpkkbm + + + SequenceFlow_1psgifi + + + + SequenceFlow_1d4t09c + SequenceFlow_14b2mg6 + + + SequenceFlow_14b2mg6 + SequenceFlow_0i9av57 + + + SequenceFlow_0i9av57 + SequenceFlow_1psgifi + + + SequenceFlow_1gpkkbm + SequenceFlow_1d4t09c + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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; + + 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 + + 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; + + 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; + + 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; + + 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; + + 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; + + 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 = ""; + + 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; + + 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; + + 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; + + 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; + + @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; + + 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 = []; + 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; + + 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; + + 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 ` + locked`; + } + + + } + + 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; + + 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 = []; + + 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; + + 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 = []; + + 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 ` + locked`; + } + } + + + + + 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; + + 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; + + 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 = []; + + @ViewChild('linechartdiv') LineChartDiv: ElementRef; + @Input() height: string; + @Input() testHeadId; + @Output() total: EventEmitter = 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; + + 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 = []; + 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; + + 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 = []; + 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 @@ + + + + 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; + + 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 @@ + + + + + + + +
+
+
+
+
+
+
+
OH!
+
+

Sorry! Page not found

+
+
+
+ + \ 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; + + 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 @@ + + + +

+ server-error works! +

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; + + 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; + + 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(); + + 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 { + 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 { + 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 { + 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; + +} + + +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; + + disabled: Boolean; + +} + +export interface BpmnInstance { + processDefinitionId: String; + deploymentId: String; + version: String; + bpmnFileId: String; + resourceFileId: String; + isDeployed: Boolean; + + testHeads: Array; + pflos: Array; + + 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; + testInstanceResults: Array; + 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; + password: String; + + groups: Array; + + favorites: Object; + + enabled: Boolean; + isVerified: Boolean; + verifyToken: String; + verifyExpires: Date; + verifyChanges: Object; + + resetToken: String; + resetExpires: DataCue; + +} + +interface GroupRef { + groupId: String; + permissions: Array; +} 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 + + + +//

Add file

+ + Content goes here + + + + + 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; + + 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, + @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; + + 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; + + 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, @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; + + 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; + + 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; + + 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, 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; + + 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 @@ + + + +
+ + +
+
+
No Input Template provided.
+
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; + + 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 = "';// + path + "'> "+ data[key] + "" + 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 = '
' + key.trim() + '
';// + path + "'> "+ data[key] + "" + 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 = '
';// + path + "'> "+ data[key] + "" + // 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 = ''; + }else{ + str = ''; + } + this.form.addControl(keyPath + key.trim(), new FormControl(data[key])); + const textAreaTemplate = '
<' + str2 + bold + ' class="mr-2">' + key.trim() + '
';// + path + "'> "+ data[key] + "" + 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 = '
<' + str + bold + ' class="mr-2">' + key.trim() + '
'; + 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 = '
' + key.trim() + ': Type Not Supported
';// + path + "'> "+ data[key] + "" + 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 @@ + + + + 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; + + 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; + + 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, + @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 @@ + + + +
+
+ + +
+
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 + + 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; + + 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, + 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; + + 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, + 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; + + 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, + 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 @@ + + + +
+
+
+
+ +
+
+
{{count}}
+
{{label}}
+
+
+
+ +
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; + + 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 = 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; + + 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, + 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; + + 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, 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; + + 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, + 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; + + 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, + 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; + + 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, + 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; + + 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, + 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; + + 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{ + 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 { + 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 { + 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 { + 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; + + 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 { + 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{ + 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> = new Subject>(); + protected selectedGroupChange: Subject = new Subject(); + + 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> { + return this.groupListChange; + } + + groupChange(): Subject { + return this.selectedGroupChange; + } + + format(arr: Array) { + + //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{ + 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 + '' + key + ':
'; + if(json.constructor === Array && toInteger(key) > 0){ + tabs--; + } + html += this.convert(json[key], ++tabs); + }else{ + html += tabHtml + '' + key + ':' + '
'; + if(typeof json[key] === 'string'){ + json[key] = json[key].replace(/\\n/g, '
' + tabHtml); + } + html += tabHtml + json[key] + '
'; + html += '
'; + } + } + } + return html; + } + + convertString(str){ + return str.replace(/\\n/g, '
'); + } +} \ 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{ + 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).unshift(data); + observer.next(init); + }else{ + (init as Array).unshift(data); + observer.next(init); + } + } + }) + .on('removed', data => { + if(init){ + if(init.data){ + init.data = (init.data as Array).filter(item => item['_id'] != data._id); + observer.next(init); + }else{ + init = (init as Array).filter(item => item['_id'] != data._id); + observer.next(init); + } + } + }) + .on('updated', data => { + if(init){ + if(init.data){ + (init.data as Array).forEach((elem, val) => { + if(elem['_id'] == data._id){ + (init.data as Array).splice(val, 1, data); + return; + } + }) + observer.next(init); + }else{ + (init as Array).forEach((elem, val) => { + if(elem['_id'] == data._id){ + (init as Array).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 { + // return this.http.get(this.path + this.Params.toString(params), this.getHttpOptions()); + // } + + find(params?): Observable { + + return this.call('find', {params: params}) + } + + // sget(id, params = []): Observable { + // return from(this.http.get(this.path + '/' + id + this.Params.toString(params), this.getHttpOptions())); + // } + + get(id, params?): Observable { + return this.call('get', {data: id, params: params}) + } + + // create(data, params = []): Observable { + // return this.http.post(this.path + this.Params.toString(params), data, this.getHttpOptions()); + // } + + create(data, params?): Observable { + return this.call('create', {data: data, params: params}) + } + + // update(data, params = []): Observable { + // return this.http.put(this.path + '/' + data._id + this.Params.toString(params), data, this.getHttpOptions()); + // } + + update(data, params?): Observable { + return this.call('update', {data: data, params: params}) + } + + // patch(data, params = []): Observable { + // return this.http.patch(this.path + '/' + data._id + this.Params.toString(params), data, this.getHttpOptions()); + // } + + patch(data, params?): Observable { + return this.call('patch', {data: data, params: params}) + } + + // delete(id, params = []): Observable { + // return this.http.delete(this.path + '/' + id + this.Params.toString(params), this.getHttpOptions()); + // } + + delete(id, params?): Observable { + 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 { + // 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{ + this.setGroup(data); + return super.create(data, params); + } + + validate(testDefinition): Observable { + 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 { + 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{ + 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 { + 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).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 { + 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{ + 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 @@ + + + + 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 + + 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 Binary files /dev/null and b/otf-frontend/client/src/assets/images/404image.png differ diff --git a/otf-frontend/client/src/assets/images/NetworkLogo.jpg b/otf-frontend/client/src/assets/images/NetworkLogo.jpg new file mode 100644 index 0000000..3c071d3 Binary files /dev/null and b/otf-frontend/client/src/assets/images/NetworkLogo.jpg differ diff --git a/otf-frontend/client/src/assets/images/OtfIcon.png b/otf-frontend/client/src/assets/images/OtfIcon.png new file mode 100644 index 0000000..1e61906 Binary files /dev/null and b/otf-frontend/client/src/assets/images/OtfIcon.png differ diff --git a/otf-frontend/client/src/assets/images/equalizer.gif b/otf-frontend/client/src/assets/images/equalizer.gif new file mode 100644 index 0000000..22ccbe3 Binary files /dev/null and b/otf-frontend/client/src/assets/images/equalizer.gif differ diff --git a/otf-frontend/client/src/assets/images/logo.png b/otf-frontend/client/src/assets/images/logo.png new file mode 100644 index 0000000..a931536 Binary files /dev/null and b/otf-frontend/client/src/assets/images/logo.png differ diff --git a/otf-frontend/client/src/assets/images/networkBackground.jpg b/otf-frontend/client/src/assets/images/networkBackground.jpg new file mode 100644 index 0000000..5b1b943 Binary files /dev/null and b/otf-frontend/client/src/assets/images/networkBackground.jpg differ diff --git a/otf-frontend/client/src/assets/images/networkBackground1.jpg b/otf-frontend/client/src/assets/images/networkBackground1.jpg new file mode 100644 index 0000000..fc4d343 Binary files /dev/null and b/otf-frontend/client/src/assets/images/networkBackground1.jpg differ diff --git a/otf-frontend/client/src/assets/images/slider1.jpg b/otf-frontend/client/src/assets/images/slider1.jpg new file mode 100644 index 0000000..9859565 Binary files /dev/null and b/otf-frontend/client/src/assets/images/slider1.jpg differ diff --git a/otf-frontend/client/src/assets/images/slider2.jpg b/otf-frontend/client/src/assets/images/slider2.jpg new file mode 100644 index 0000000..794d791 Binary files /dev/null and b/otf-frontend/client/src/assets/images/slider2.jpg differ diff --git a/otf-frontend/client/src/assets/workflows/blank.bpmn b/otf-frontend/client/src/assets/workflows/blank.bpmn new file mode 100644 index 0000000..d310b7e --- /dev/null +++ b/otf-frontend/client/src/assets/workflows/blank.bpmn @@ -0,0 +1,13 @@ + + + + + + + + + + + + + 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 Binary files /dev/null and b/otf-frontend/client/src/favicon.ico differ diff --git a/otf-frontend/client/src/global-shims.ts b/otf-frontend/client/src/global-shims.ts new file mode 100644 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 @@ + + + + + + + + Open Test Framework + + + + + + + +
+
+
+
+
+
+ + 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 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.' + '
' + 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!' + '
' + '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.' + '
' + + 'Details: ' + '
' + + ' Email: ' + user['email'] + '
' + + ' First Name: ' + user['firstName'] + '
' + + ' Last Name: ' + user['lastName'] + '
' + + '
' + + 'Enable their account by visiting ' + '
' + 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.' + '
' + + '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'] + "
" + + "First Name: " + user['firstName'] + "
" + + "Last Name: " + user['lastName'] + "
" + + "Message: " + message + "
" + + "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('') !== -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('') !== -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/helm/otf-service-api/values.yaml b/otf-service-api/helm/otf-service-api/values.yaml new file mode 100644 index 0000000..49ee641 --- /dev/null +++ b/otf-service-api/helm/otf-service-api/values.yaml @@ -0,0 +1,89 @@ +appName: otf-service-api +version: 0.0.1-SNAPSHOT +image: otf-service-api:0.0.1-SNAPSHOT +namespace: org-oran-otf +nodePort: 32303 +replicas: + dev: 2 + st: 1 + prod: 2 +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: "!" + password: "!" + camunda: + dev: + host: https://localhost + port: 31313 + st: + host: https://localhost + port: 31313 + prod: + host: https://localhost + port: 31313 + prod_dr: + host: https://localhost + port: 31313 + uri: + process_definition: rest/process-definition/key + delete_test_strategy: otf/tcu/delete-test-strategy/v1/deployment-id + delete_test_strategy_test_definition_id: otf/tcu/delete-test-strategy/v1/test-definition-id + execute_test: otf/tcu/execute/workflowRequest + deploy_test_strategy_zip: otf/tcu/deploy-test-strategy-zip/v1 + process_instance_completion_check: otf/tcu/process-instance-completion-check/v1 + health: /otf/health/v1 + executionUri: otf/tcu/execute-test/v1 + pollingUri: otf/tcu/process-instance-completion-check/v1 + deploymentUri: otf/tcu/deploy-test-strategy-zip/v1 + processDefinitionKeyUri: rest/process-definition/key + deploymentDeletionUri: otf/tcu/delete-test-strategy/v1/deployment-id + testDefinitionDeletionUri: otf/tcu/delete-test-strategy/v1/test-definition-id + api: + poll_interval: 6000 + poll_attempts: 50 + +# permission type for aaf +aafPermType: + dev: org.oran.otf.svcapi + st: org.oran.otf.st.svcapi + prod: org.oran.otf.prod.svcapi + +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 + +pvc: + dev: org-oran-otf-dev-logs-pv + prod: org-oran-otf-prod-logs-pv + + 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 @@ + + + otf-service-api + + otf-service-api + + + maven-compiler-plugin + + 1.8 + 1.8 + + org.apache.maven.plugins + + + spring-boot-maven-plugin + org.springframework.boot + + + swagger-maven-plugin + + openapi + + JSONANDYAML + true + + org.oran.otf.api + + + + + + resolve + + compile + + + io.swagger.core.v3 + 2.0.7 + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + ${skipUTs} + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.22.1 + + + run-integration-tests + integration-test + + integration-test + verify + + + + + ${skipTests} + ${skipITs} + + + + + + + src/main/resources + + otf_dev.p12 + + true + + **/* + + ${basedir}/target/src/main/resources + + + src/main/resources + + otf_dev.p12 + + true + + **/* + + + + src/main/resources + + otf_dev.p12 + + ${basedir}/target/src/main/resources + + + src/main/resources + + otf_dev.p12 + + + + docker + + Dockerfile + + ${basedir}/target + + + + + + spring-boot-starter + org.springframework.boot + + + + spring-boot-starter-web + org.springframework.boot + + + + spring-boot-starter-jersey + org.springframework.boot + + + + + org.glassfish.jersey.test-framework + jersey-test-framework-core + test + + + org.glassfish.jersey.test-framework.providers + jersey-test-framework-provider-grizzly2 + test + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + test + + + com.github.tomakehurst + wiremock-jre8 + 2.24.0 + test + + + org.mockito + mockito-core + 2.15.0 + test + + + org.mockito + mockito-inline + test + + + io.rest-assured + rest-assured + 4.0.0 + test + + + io.rest-assured + rest-assured-all + 4.0.0 + test + + + + + + spring-boot-starter-test + org.springframework.boot + test + + + com.vaadin.external.google + android-json + + + + + + spring-boot-starter-data-mongodb + org.springframework.boot + + + + swagger-jaxrs2 + io.swagger.core.v3 + 2.0.7 + + + + swagger-jaxrs2-servlet-initializer + io.swagger.core.v3 + 2.0.7 + + + + swagger-annotations + io.swagger.core.v3 + 2.0.7 + + + + springfox-swagger2 + io.springfox + 2.9.2 + + + + springfox-swagger-ui + io.springfox + 2.9.2 + + + + springfox-bean-validators + io.springfox + 2.9.2 + + + + httpclient + org.apache.httpcomponents + + + + h2 + com.h2database + + + + + + + + + + gson + com.google.code.gson + 2.8.5 + + + + + aaf-auth-client + org.onap.aaf.authz + ${cadi.version} + + + + aaf-cadi-core + org.onap.aaf.authz + ${cadi.version} + + + + aaf-cadi-aaf + org.onap.aaf.authz + ${cadi.version} + + + + json + org.json + 20180813 + + + + jackson-annotations + com.fasterxml.jackson.core + + + + jackson-core + com.fasterxml.jackson.core + + + + jackson-databind + com.fasterxml.jackson.core + + + + jersey-media-multipart + org.glassfish.jersey.media + 2.27 + + + + httpmime + org.apache.httpcomponents + 4.5.7-SNAPSHOT + + + + httpasyncclient + org.apache.httpcomponents + 4.1.4 + + + net.java.dev.jna + jna-platform + + + + Service API - OTF + org.oran.otf + + 4.0.0 + + otf-service-api + + jar + + + spring-boot-starter-parent + org.springframework.boot + 2.1.3.RELEASE + + + + false + ${skipTests} + ${skipTests} + 2.1.10 + registry.hub.docker.io + 1.8 + org.oran.otf + UTF-8 + UTF-8 + + Camille.1.0 + + + \ 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 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 boolean identifierExistsInCollection( + MongoRepository repository, ObjectId identifier) { + return repository.findById(identifier.toString()).isPresent(); + } + + public static T findByIdGeneric(MongoRepository repository, ObjectId identifier) { + Optional 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 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 resolveOptional(Optional optional) { + return optional.orElse(null); + } + + public static T mapRequest(Class 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 replaceObjectId(Map 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 cadiFilterRegistration() { + FilterRegistrationBean 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 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 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 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> 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 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 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 otfApiEnforcementFilterRegistration() + throws ServletException { + FilterRegistrationBean 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 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 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 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 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() {}); + 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 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 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 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 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 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 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 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 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 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 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 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 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 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 mechanizedIds; + private ObjectId ownerId; + private List roles; + private List 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 getMechanizedIds() { + return mechanizedIds; + } + + public void setMechanizedIds(List mechanizedIds) { + this.mechanizedIds = mechanizedIds; + } + + public ObjectId getOwnerId() { + return ownerId; + } + + public void setOwnerId(ObjectId ownerId) { + this.ownerId = ownerId; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public List getMembers() { + return members; + } + + public void setMembers(List 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 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 getRoles() { + return roles; + } + + public void setRoles(List 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 permissions; + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List 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 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 getBpmnInstances() { + return bpmnInstances; + } + + public void setBpmnInstances(List 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 testDetails; + private List testHeadResults; + private List 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 testDetails, + List testHeadResults, + List 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 getTestDetails() { + return testDetails; + } + + public void setTestDetails(Map testDetails) { + this.testDetails = testDetails; + } + + public List getTestHeadResults() { + synchronized (testHeadResults) { + return testHeadResults; + } + } + + public void setTestHeadResults(List testHeadResults) { + synchronized (testHeadResults) { + this.testHeadResults = testHeadResults; + } + } + + public List getTestInstanceResults() { + synchronized (testInstanceResults) { + return testInstanceResults; + } + } + + public void setTestInstanceResults(List 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 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 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 getVthInputTemplate() { + return vthInputTemplate; + } + + public void setVthInputTemplate(Map 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 pfloInput; + private HashMap internalTestData; + private HashMap simulationVthInput; + private HashMap testData; + private HashMap 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 pfloInput, + HashMap internalTestData, + HashMap simulationVthInput, + HashMap testData, + HashMap 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 getPfloInput() { + return pfloInput; + } + + public void setPfloInput(HashMap pfloInput) { + this.pfloInput = pfloInput; + } + + public HashMap getInternalTestData() { + return internalTestData; + } + + public void setInternalTestData(HashMap internalTestData) { + this.internalTestData = internalTestData; + } + + public HashMap getSimulationVthInput() { + return simulationVthInput; + } + + public void setSimulationVthInput(HashMap simulationVthInput) { + this.simulationVthInput = simulationVthInput; + } + + public HashMap getTestData() { + return testData; + } + + public void setTestData(HashMap testData) { + this.testData = testData; + } + + public HashMap getVthInput() { + return vthInput; + } + + public void setVthInput(HashMap 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 permissions; + private String firstName; + private String lastName; + private String email; + private String password; + private List groups; + private Date createdAt; + private Date updatedAt; + + //Added User for testing + public User(){}; + + public User( + ObjectId _id, + List permissions, + String firstName, + String lastName, + String email, + String password, + List 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 getPermissions() { + return permissions; + } + + public void setPermissions(List 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 getGroups() { + return groups; + } + + public void setGroups(List 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 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 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 getHistoricBpmnInstanceAsList( + List bpmnInstances, String processDefinitionId) { + BpmnInstance bpmnInstance = + bpmnInstances.stream() + .filter( + _bpmnInstance -> + _bpmnInstance.getProcessDefinitionId().equalsIgnoreCase(processDefinitionId)) + .findFirst() + .orElse(null); + + List 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 getBpmnInstances() { + return bpmnInstances; + } + + public void setBpmnInstances(List 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 pfloInput; + private Map simulationVthInput; + private Map testData; + private Map 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 pfloInput, + HashMap simulationVthInput, + HashMap testData, + HashMap 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 getPfloInput() { + return pfloInput; + } + + public void setPfloInput( + HashMap pfloInput) { + this.pfloInput = pfloInput; + } + + public Map getSimulationVthInput() { + return simulationVthInput; + } + + public void setSimulationVthInput( + HashMap simulationVthInput) { + this.simulationVthInput = simulationVthInput; + } + + public Map getTestData() { + return testData; + } + + public void setTestData(HashMap testData) { + this.testData = testData; + } + + public Map getVthInput() { + return vthInput; + } + + public void setVthInput(HashMap 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 testHeads; + private List pflos; + private Map 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 testHeads, + @JsonProperty("plfos") List pflos, + @JsonProperty("testDataTemplate") Map 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 getTestHeads() { + return testHeads; + } + + public void setTestHeads(List testHeads) { + this.testHeads = testHeads; + } + + public List getPflos() { + return pflos; + } + + public void setPflos(List pflos) { + this.pflos = pflos; + } + + public Map getTestDataTemplate() { + return testDataTemplate; + } + + public void setTestDataTemplate(Map 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 args; + private boolean interruptOnFailure; + private int maxFailures; + private int threadPoolSize; + + public ParallelFlowInput() {} + + public ParallelFlowInput( + List 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 getArgs() { + return args; + } + + public void setArgs(List 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 headers; + private Map body; + + public TestHeadRequest(){} + + public TestHeadRequest(Map headers, + Map body) { + this.headers = headers; + this.body = body; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Map getBody() { + return body; + } + + public void setBody(Map 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 testHeadHeaders; + //private int testHeadCode; + private int statusCode; + + private TestHeadRequest testHeadRequest; + private Map 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 to RequestContent + int statusCode, + + TestHeadRequest testHeadRequest, + Map 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 getTestHeadResponse() { + return testHeadResponse; + } + + public void setTestHeadResponse(Map 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 pfloInput; + private HashMap simulationVthInput; + private HashMap testData; + private HashMap 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 pfloInput, + HashMap simulationVthInput, + HashMap testData, + HashMap 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 getPfloInput() { + return pfloInput; + } + + public void setPfloInput(HashMap pfloInput) { + this.pfloInput = pfloInput; + } + + public HashMap getSimulationVthInput() { + return simulationVthInput; + } + + public void setSimulationVthInput(HashMap simulationVthInput) { + this.simulationVthInput = simulationVthInput; + } + + public HashMap getTestData() { + return testData; + } + + public void setTestData(HashMap testData) { + this.testData = testData; + } + + public HashMap getVthInput() { + return vthInput; + } + + public void setVthInput(HashMap 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 permissions; + + public UserGroup(){} + public UserGroup(ObjectId groupId, List permissions) { + this.groupId = groupId; + this.permissions = permissions; + } + + public ObjectId getGroupId() { + return groupId; + } + + public void setGroupId(ObjectId groupId) { + this.groupId = groupId; + } + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List 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 pfloInput = null; + private Map testData = null; + private Map vthInput = null; + private long maxExecutionTimeInMillis = 0L; + + public WorkflowRequest() throws Exception { + this.validate(); + } + + public WorkflowRequest( + boolean async, + ObjectId executorId, + ObjectId testInstanceId, + Map pfloInput, + Map testData, + Map 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 pfloInput, + Map testData, + Map 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 getPfloInput() { + return pfloInput; + } + + public void setPfloInput(Map pfloInput) { + this.pfloInput = pfloInput; + } + + public Map getTestData() { + return testData; + } + + public void setTestData(Map testData) { + this.testData = testData; + } + + public Map getVthInput() { + return vthInput; + } + + public void setVthInput(Map 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 { + @Query("{ 'members.userId': ?0 }") + public List 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 { + + Optional 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 { + + Optional 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 { + Optional 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 { + + Optional findByTestInstanceName(String testInstanceName); + + @Query("{ 'testDefinitionId': ?0 }") + List findAllByTestDefinitionId(ObjectId testDefinitionId); + + @Query("{ 'testDefinitionId': ?0, 'processDefinitionId': ?1 }") + List 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 { + Optional 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 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 boolean identifierExistsInCollection( + MongoRepository repository, ObjectId identifier) { + return repository.findById(identifier.toString()).isPresent(); + } + + public static T findByIdGeneric(MongoRepository repository, ObjectId identifier) { + Optional 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() { + @Override + public JsonElement serialize( + ObjectId src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.toHexString()); + } + }) + .registerTypeAdapter( + ObjectId.class, + new JsonDeserializer() { + @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 jsonToMap(String json) { + Type type = new TypeToken>() { + }.getType(); + return getGson().fromJson(json, type); + } + + public static String objectToJson(Object obj) { + return getGson().toJson(obj); + } + + public static T mapToObject(Map map, TypeReference typeReference) throws IOException { + return jsonToObject(mapToJson(map), typeReference); + } + + public static T jsonToObject(String json, TypeReference 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() { + @Override + public JsonElement serialize(ObjectId src, Type typeOfSrc, + JsonSerializationContext context) { + return new JsonPrimitive(src.toHexString()); + } + }).registerTypeAdapter(ObjectId.class, new JsonDeserializer() { + @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>(){}.getType(); + + public static Map jsonToMapStringString(String json) { + Map ret = new HashMap(); + if (json == null || json.isEmpty()) + return ret; + return gson.fromJson(json, TT_mapStringString); + } + public static String mapStringObjectToJson(Map map) { + if (map == null) + map = new HashMap(); + 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 headers) + throws IOException, InterruptedException, ExecutionException { + HttpPost post = buildPost(url, body, headers); + executeAsync(post); + } + + public static HttpResponse postSync(String url, String body, Map headers) + throws IOException, InterruptedException, ExecutionException { + HttpPost post = buildPost(url, body, headers); + return executeSync(post); + } + + public static HttpResponse postSync( + String url, String body, Map headers, int timeoutInMillis) + throws IOException, InterruptedException, ExecutionException { + HttpPost post = buildPost(url, body, headers); + return executeSync(post, timeoutInMillis); + } + + public static HttpResponse getSync(String url, Map headers) + throws IOException, InterruptedException, ExecutionException { + HttpGet get = buildGet(url, headers); + return executeSync(get); + } + + public static HttpResponse getSync(String url, Map 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 headers) throws IOException { + HttpGet get = buildGet(url, headers); + executeAsync(get); + } + + private static HttpPost buildPost(String url, String body, Map 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 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 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 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 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> 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 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 getUserGroupPermissions(User user, Group group){ + Set permissionsAllowed = new HashSet<>(); + Set 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 getRolePermissions(String userRoleName,Group group) + { + for(Role role : group.getRoles()) + { + if(role.getRoleName().equalsIgnoreCase(userRoleName)) + { + return new HashSet(role.getPermissions()); + } + } + return new HashSet(); // 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 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(member.getRoles()); + } + return new HashSet(); //if user have no roles + } + // create map that where key is the group id and value = users permission (string) that that group + private Map> mapGroupsToPermission(User user, GroupRepository groupRepository){ + Map> groupAccessMap = new HashMap<>(); + List enrolledGroups = groupRepository.findAllByMembersId(user.get_id());// enrolledGroups = groups that user is a member of + Map allGroupMap = groupListToMap(groupRepository.findAll()); + // get all permission in the groups the user is ia member of + for(Group group: enrolledGroups) { + Set permissions = getUserGroupPermissions(user,group); + groupAccessMap.put(group.get_id().toString(),convertPermissions(permissions)); + } + //assign add read to all parent groups + Set 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(Arrays.asList(UserPermission.Permission.READ))); + } + // if there is management role + // then assign read access to children + if(hasManagementRole(user,enrolledGroups)){ +// SetchildIds = 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 childIds= getChildrenGroupsId(Arrays.asList(enrolledGroup),allGroupMap,user); + Set 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 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 getParentGroups(List enrolledGroup,Map groupMap ) + { + Set parentGroups = new HashSet<>(); + return lookUp(enrolledGroup,groupMap,parentGroups); + } + //recursive lookup starting at the enrolled groups that the user is a member of + private Set lookUp(List groupsToCheck, Map groupMap,Set 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 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 groupListToMap(List allGroups) + { + Map groupMap = new HashMap<>(); + allGroups.forEach(group -> groupMap.put(group.get_id().toString(),group)); + return groupMap; + } + //get all the child group + private Set getChildrenGroupsId(List enrolledGroup, Map allGroupsMap, User user) + { + Set childrenGroups = new HashSet<>(); + Set managementGroupIds = getManagementGroupIds(enrolledGroup,user); + return lookForChildren(managementGroupIds,allGroupsMap,childrenGroups); + } + + private Set getManagementGroupIds(List enrolledGroups,User user) + { + Set 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 lookForChildren (Set parentIds, Map allGroupsMap, Set resultSet) + { + //base case = no groups to check anymore; + if (parentIds.isEmpty()) + return resultSet; + + Set 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(SetparentGroupIds, 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 convertPermissions (Set permissions){ + Set 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> userAccessMap; + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public Map> getUserAccessMap() { + return userAccessMap; + } + + public void setUserAccessMap(Map> userAccessMap) { + this.userAccessMap = userAccessMap; + } + + public boolean hasAccessTo(String groupId,String permission) { + if (userAccessMap.get(groupId) == null) { + return false; + } + Set 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 Binary files /dev/null and b/otf-service-api/src/main/resources/truststore2018.jks differ diff --git a/otf-service-api/src/test/java/org/oran/otf/api/tests/config/DataConfig2.java b/otf-service-api/src/test/java/org/oran/otf/api/tests/config/DataConfig2.java new file mode 100644 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 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 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 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 parentMemberRoles = PermissionUtil.findUserRoles(parentUserMock, parentGroup); + Set firstChildRoles = PermissionUtil.findUserRoles(firstChildUserMock, firstChildGroup); + Set 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 roles = PermissionUtil.findUserRoles(firstChildUserMock,firstChildGroup); //dev and admin roles only + + Assert.assertEquals(2,roles.size()); + for(String role : roles){ + Set 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 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> parentAccessControl = parentUserPermissions.getUserAccessMap(); + Map> firstChildAccessControl = firstChildUserPermissions.getUserAccessMap(); + Map> 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 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 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 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 usersFirstNames = new ArrayList<>(Arrays.asList("Joe", "Jim", "Rick", "David", "Tony")); + List 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 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 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 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 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> userAccessMap ; + + @InjectMocks + private UserPermission userPermission; + + @Before + public void setUp() + { + String fakeGroupId1 = "abc123"; + Set 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