Merge branch 'dev' into feature/approval_request_script
This commit is contained in:
commit
04e5a476c4
|
@ -1,7 +1,7 @@
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
python:
|
python:
|
||||||
- "3.6.9"
|
- "3.7"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- postgresql
|
- postgresql
|
||||||
|
|
32
Dockerfile
32
Dockerfile
|
@ -1,24 +1,22 @@
|
||||||
FROM python:3.6.9-slim
|
FROM python:3.7-slim
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY Pipfile Pipfile.lock /app/
|
COPY Pipfile Pipfile.lock /app/
|
||||||
|
|
||||||
RUN pip install pipenv && \
|
RUN set -xe \
|
||||||
apt-get update && \
|
&& pip install pipenv \
|
||||||
apt-get install -y --no-install-recommends \
|
&& apt-get update -q \
|
||||||
|
&& apt-get install -y -q \
|
||||||
gcc python3-dev libssl-dev \
|
gcc python3-dev libssl-dev \
|
||||||
curl postgresql-client git-core && \
|
curl postgresql-client git-core \
|
||||||
pipenv install --dev && \
|
gunicorn3 postgresql-client \
|
||||||
apt-get remove -y gcc python3-dev libssl-dev && \
|
&& pipenv install --dev \
|
||||||
apt-get purge -y --auto-remove && \
|
&& apt-get remove -y gcc python3-dev libssl-dev \
|
||||||
rm -rf /var/lib/apt/lists/ *
|
&& apt-get autoremove -y \
|
||||||
|
&& apt-get clean -y \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& mkdir -p /app \
|
||||||
|
&& useradd _gunicorn --no-create-home --user-group
|
||||||
|
|
||||||
COPY . /app/
|
COPY . /app/
|
||||||
|
WORKDIR /app
|
||||||
ENV FLASK_APP=/app/crc/__init__.py
|
|
||||||
CMD ["pipenv", "run", "flask", "db", "upgrade"]
|
|
||||||
CMD ["pipenv", "run", "python", "/app/run.py"]
|
|
||||||
|
|
||||||
# expose ports
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
5
Pipfile
5
Pipfile
|
@ -5,6 +5,7 @@ verify_ssl = true
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
pytest = "*"
|
pytest = "*"
|
||||||
|
pbr = "*"
|
||||||
|
|
||||||
[packages]
|
[packages]
|
||||||
connexion = {extras = ["swagger-ui"],version = "*"}
|
connexion = {extras = ["swagger-ui"],version = "*"}
|
||||||
|
@ -35,6 +36,8 @@ python-dateutil = "*"
|
||||||
pandas = "*"
|
pandas = "*"
|
||||||
xlrd = "*"
|
xlrd = "*"
|
||||||
ldap3 = "*"
|
ldap3 = "*"
|
||||||
|
gunicorn = "*"
|
||||||
|
werkzeug = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.6.9"
|
python_version = "3.7"
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "1ca737db75750ea4351c15b4b0b26155d90bc5522705ed293a0c2773600b6a0a"
|
"sha256": "fad2f86b02a85b074e8ff9d8f3822784444d0925b0bf4227d4b26bbff1c45c2f"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
"python_version": "3.6.9"
|
"python_version": "3.7"
|
||||||
},
|
},
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
|
@ -235,11 +235,11 @@
|
||||||
},
|
},
|
||||||
"docxtpl": {
|
"docxtpl": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:16a76d360c12f7da3a28821fc740b9a84b891895233493ff0b002ffaa6026905",
|
"sha256:0e031ea5da63339f2bac0fd7eb7b3b137303571a9a92c950501148240ea22047",
|
||||||
"sha256:f19adf2a713a753c1e056ef0ce395bc8da62d495b091ebf9fe67dfc6d1115f9f"
|
"sha256:45f04661b9ab1fd66b975a0a547b30c8811f457bef2f85249c2f3c5784a00052"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.9.2"
|
"version": "==0.10.0"
|
||||||
},
|
},
|
||||||
"et-xmlfile": {
|
"et-xmlfile": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -296,10 +296,10 @@
|
||||||
},
|
},
|
||||||
"flask-sqlalchemy": {
|
"flask-sqlalchemy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327",
|
"sha256:0b656fbf87c5f24109d859bafa791d29751fabbda2302b606881ae5485b557a5",
|
||||||
"sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d"
|
"sha256:fcfe6df52cd2ed8a63008ca36b86a51fa7a4b70cef1c39e5625f722fca32308e"
|
||||||
],
|
],
|
||||||
"version": "==2.4.1"
|
"version": "==2.4.3"
|
||||||
},
|
},
|
||||||
"future": {
|
"future": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -307,6 +307,14 @@
|
||||||
],
|
],
|
||||||
"version": "==0.18.2"
|
"version": "==0.18.2"
|
||||||
},
|
},
|
||||||
|
"gunicorn": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626",
|
||||||
|
"sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==20.0.4"
|
||||||
|
},
|
||||||
"httpretty": {
|
"httpretty": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:24a6fd2fe1c76e94801b74db8f52c0fb42718dc4a199a861b305b1a492b9d868"
|
"sha256:24a6fd2fe1c76e94801b74db8f52c0fb42718dc4a199a861b305b1a492b9d868"
|
||||||
|
@ -719,11 +727,11 @@
|
||||||
},
|
},
|
||||||
"sphinx": {
|
"sphinx": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:62edfd92d955b868d6c124c0942eba966d54b5f3dcb4ded39e65f74abac3f572",
|
"sha256:779a519adbd3a70fc7c468af08c5e74829868b0a5b34587b33340e010291856c",
|
||||||
"sha256:f5505d74cf9592f3b997380f9bdb2d2d0320ed74dd69691e3ee0644b956b8d83"
|
"sha256:ea64df287958ee5aac46be7ac2b7277305b0381d213728c3a49d8bb9b8415807"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.0.3"
|
"version": "==3.0.4"
|
||||||
},
|
},
|
||||||
"sphinxcontrib-applehelp": {
|
"sphinxcontrib-applehelp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -770,7 +778,7 @@
|
||||||
"spiffworkflow": {
|
"spiffworkflow": {
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
||||||
"ref": "cb098ee6d55b85bf7795997f4ad5f78c27d15381"
|
"ref": "c8d87826d496af825a184bdc3f0a751e603cfe44"
|
||||||
},
|
},
|
||||||
"sqlalchemy": {
|
"sqlalchemy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -855,6 +863,7 @@
|
||||||
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
|
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
|
||||||
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
|
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"version": "==1.0.1"
|
"version": "==1.0.1"
|
||||||
},
|
},
|
||||||
"xlrd": {
|
"xlrd": {
|
||||||
|
@ -911,6 +920,14 @@
|
||||||
],
|
],
|
||||||
"version": "==20.4"
|
"version": "==20.4"
|
||||||
},
|
},
|
||||||
|
"pbr": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c",
|
||||||
|
"sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==5.4.5"
|
||||||
|
},
|
||||||
"pluggy": {
|
"pluggy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
|
||||||
|
|
|
@ -13,6 +13,9 @@ DEVELOPMENT = environ.get('DEVELOPMENT', default="true") == "true"
|
||||||
TESTING = environ.get('TESTING', default="false") == "true"
|
TESTING = environ.get('TESTING', default="false") == "true"
|
||||||
PRODUCTION = (environ.get('PRODUCTION', default="false") == "true") or (not DEVELOPMENT and not TESTING)
|
PRODUCTION = (environ.get('PRODUCTION', default="false") == "true") or (not DEVELOPMENT and not TESTING)
|
||||||
|
|
||||||
|
# Add trailing slash to base path
|
||||||
|
APPLICATION_ROOT = re.sub(r'//', '/', '/%s/' % environ.get('APPLICATION_ROOT', default="/").strip('/'))
|
||||||
|
|
||||||
DB_HOST = environ.get('DB_HOST', default="localhost")
|
DB_HOST = environ.get('DB_HOST', default="localhost")
|
||||||
DB_PORT = environ.get('DB_PORT', default="5432")
|
DB_PORT = environ.get('DB_PORT', default="5432")
|
||||||
DB_NAME = environ.get('DB_NAME', default="crc_dev")
|
DB_NAME = environ.get('DB_NAME', default="crc_dev")
|
||||||
|
@ -22,27 +25,20 @@ SQLALCHEMY_DATABASE_URI = environ.get(
|
||||||
'SQLALCHEMY_DATABASE_URI',
|
'SQLALCHEMY_DATABASE_URI',
|
||||||
default="postgresql://%s:%s@%s:%s/%s" % (DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME)
|
default="postgresql://%s:%s@%s:%s/%s" % (DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME)
|
||||||
)
|
)
|
||||||
TOKEN_AUTH_TTL_HOURS = environ.get('TOKEN_AUTH_TTL_HOURS', default=4)
|
TOKEN_AUTH_TTL_HOURS = int(environ.get('TOKEN_AUTH_TTL_HOURS', default=4))
|
||||||
TOKEN_AUTH_SECRET_KEY = environ.get('TOKEN_AUTH_SECRET_KEY', default="Shhhh!!! This is secret! And better darn well not show up in prod.")
|
TOKEN_AUTH_SECRET_KEY = environ.get('TOKEN_AUTH_SECRET_KEY', default="Shhhh!!! This is secret! And better darn well not show up in prod.")
|
||||||
FRONTEND_AUTH_CALLBACK = environ.get('FRONTEND_AUTH_CALLBACK', default="http://localhost:4200/session")
|
FRONTEND_AUTH_CALLBACK = environ.get('FRONTEND_AUTH_CALLBACK', default="http://localhost:4200/session")
|
||||||
SWAGGER_AUTH_KEY = environ.get('SWAGGER_AUTH_KEY', default="SWAGGER")
|
SWAGGER_AUTH_KEY = environ.get('SWAGGER_AUTH_KEY', default="SWAGGER")
|
||||||
|
|
||||||
# %s/%i placeholders expected for uva_id and study_id in various calls.
|
# %s/%i placeholders expected for uva_id and study_id in various calls.
|
||||||
PB_ENABLED = environ.get('PB_ENABLED', default=True)
|
PB_ENABLED = environ.get('PB_ENABLED', default="false") == "true"
|
||||||
PB_BASE_URL = environ.get('PB_BASE_URL', default="http://localhost:5001/pb/")
|
PB_BASE_URL = environ.get('PB_BASE_URL', default="http://localhost:5001/pb/").strip('/') + '/' # Trailing slash required
|
||||||
PB_USER_STUDIES_URL = environ.get('PB_USER_STUDIES_URL', default=PB_BASE_URL + "user_studies?uva_id=%s")
|
PB_USER_STUDIES_URL = environ.get('PB_USER_STUDIES_URL', default=PB_BASE_URL + "user_studies?uva_id=%s")
|
||||||
PB_INVESTIGATORS_URL = environ.get('PB_INVESTIGATORS_URL', default=PB_BASE_URL + "investigators?studyid=%i")
|
PB_INVESTIGATORS_URL = environ.get('PB_INVESTIGATORS_URL', default=PB_BASE_URL + "investigators?studyid=%i")
|
||||||
PB_REQUIRED_DOCS_URL = environ.get('PB_REQUIRED_DOCS_URL', default=PB_BASE_URL + "required_docs?studyid=%i")
|
PB_REQUIRED_DOCS_URL = environ.get('PB_REQUIRED_DOCS_URL', default=PB_BASE_URL + "required_docs?studyid=%i")
|
||||||
PB_STUDY_DETAILS_URL = environ.get('PB_STUDY_DETAILS_URL', default=PB_BASE_URL + "study?studyid=%i")
|
PB_STUDY_DETAILS_URL = environ.get('PB_STUDY_DETAILS_URL', default=PB_BASE_URL + "study?studyid=%i")
|
||||||
|
|
||||||
LDAP_URL = environ.get('LDAP_URL', default="ldap.virginia.edu")
|
LDAP_URL = environ.get('LDAP_URL', default="ldap.virginia.edu").strip('/') # No trailing slash or http://
|
||||||
LDAP_TIMEOUT_SEC = environ.get('LDAP_TIMEOUT_SEC', default=3)
|
LDAP_TIMEOUT_SEC = int(environ.get('LDAP_TIMEOUT_SEC', default=3))
|
||||||
print('=== USING DEFAULT CONFIG: ===')
|
|
||||||
print('DB_HOST = ', DB_HOST)
|
|
||||||
print('CORS_ALLOW_ORIGINS = ', CORS_ALLOW_ORIGINS)
|
|
||||||
print('DEVELOPMENT = ', DEVELOPMENT)
|
|
||||||
print('TESTING = ', TESTING)
|
|
||||||
print('PRODUCTION = ', PRODUCTION)
|
|
||||||
print('PB_BASE_URL = ', PB_BASE_URL)
|
|
||||||
print('LDAP_URL = ', LDAP_URL)
|
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,29 @@
|
||||||
import os
|
import os
|
||||||
|
from os import environ
|
||||||
|
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
NAME = "CR Connect Workflow"
|
NAME = "CR Connect Workflow"
|
||||||
DEVELOPMENT = True
|
DEVELOPMENT = True
|
||||||
TESTING = True
|
TESTING = True
|
||||||
SQLALCHEMY_DATABASE_URI = "postgresql://crc_user:crc_pass@localhost:5432/crc_test"
|
|
||||||
TOKEN_AUTH_SECRET_KEY = "Shhhh!!! This is secret! And better darn well not show up in prod."
|
TOKEN_AUTH_SECRET_KEY = "Shhhh!!! This is secret! And better darn well not show up in prod."
|
||||||
PB_ENABLED = False
|
PB_ENABLED = False
|
||||||
|
|
||||||
|
# This is here, for when we are running the E2E Tests in the frontend code bases.
|
||||||
|
# which will set the TESTING envronment to true, causing this to execute, but we need
|
||||||
|
# to respect the environment variables in that case.
|
||||||
|
# when running locally the defaults apply, meaning we use crc_test for doing the tests
|
||||||
|
# locally, and we don't over-write the database. Did you read this far? Have a cookie!
|
||||||
|
DB_HOST = environ.get('DB_HOST', default="localhost")
|
||||||
|
DB_PORT = environ.get('DB_PORT', default="5432")
|
||||||
|
DB_NAME = environ.get('DB_NAME', default="crc_test")
|
||||||
|
DB_USER = environ.get('DB_USER', default="crc_user")
|
||||||
|
DB_PASSWORD = environ.get('DB_PASSWORD', default="crc_pass")
|
||||||
|
SQLALCHEMY_DATABASE_URI = environ.get(
|
||||||
|
'SQLALCHEMY_DATABASE_URI',
|
||||||
|
default="postgresql://%s:%s@%s:%s/%s" % (DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME)
|
||||||
|
)
|
||||||
|
|
||||||
print('### USING TESTING CONFIG: ###')
|
print('### USING TESTING CONFIG: ###')
|
||||||
print('SQLALCHEMY_DATABASE_URI = ', SQLALCHEMY_DATABASE_URI)
|
print('SQLALCHEMY_DATABASE_URI = ', SQLALCHEMY_DATABASE_URI)
|
||||||
print('DEVELOPMENT = ', DEVELOPMENT)
|
print('DEVELOPMENT = ', DEVELOPMENT)
|
||||||
|
|
|
@ -8,18 +8,7 @@ SQLALCHEMY_DATABASE_URI = "postgresql://postgres:@localhost:5432/crc_test"
|
||||||
TOKEN_AUTH_TTL_HOURS = 2
|
TOKEN_AUTH_TTL_HOURS = 2
|
||||||
TOKEN_AUTH_SECRET_KEY = "Shhhh!!! This is secret! And better darn well not show up in prod."
|
TOKEN_AUTH_SECRET_KEY = "Shhhh!!! This is secret! And better darn well not show up in prod."
|
||||||
FRONTEND_AUTH_CALLBACK = "http://localhost:4200/session" # Not Required
|
FRONTEND_AUTH_CALLBACK = "http://localhost:4200/session" # Not Required
|
||||||
|
PB_ENABLED = False
|
||||||
#: Default attribute map for single signon.
|
|
||||||
SSO_ATTRIBUTE_MAP = {
|
|
||||||
'eppn': (False, 'eppn'), # dhf8r@virginia.edu
|
|
||||||
'uid': (True, 'uid'), # dhf8r
|
|
||||||
'givenName': (False, 'first_name'), # Daniel
|
|
||||||
'mail': (False, 'email_address'), # dhf8r@Virginia.EDU
|
|
||||||
'sn': (False, 'last_name'), # Funk
|
|
||||||
'affiliation': (False, 'affiliation'), # 'staff@virginia.edu;member@virginia.edu'
|
|
||||||
'displayName': (False, 'display_name'), # Daniel Harold Funk
|
|
||||||
'title': (False, 'title') # SOFTWARE ENGINEER V
|
|
||||||
}
|
|
||||||
|
|
||||||
print('+++ USING TRAVIS TESTING CONFIG: +++')
|
print('+++ USING TRAVIS TESTING CONFIG: +++')
|
||||||
print('SQLALCHEMY_DATABASE_URI = ', SQLALCHEMY_DATABASE_URI)
|
print('SQLALCHEMY_DATABASE_URI = ', SQLALCHEMY_DATABASE_URI)
|
||||||
|
|
|
@ -34,12 +34,22 @@ ma = Marshmallow(app)
|
||||||
from crc import models
|
from crc import models
|
||||||
from crc import api
|
from crc import api
|
||||||
|
|
||||||
connexion_app.add_api('api.yml')
|
connexion_app.add_api('api.yml', base_path='/v1.0')
|
||||||
|
|
||||||
# Convert list of allowed origins to list of regexes
|
# Convert list of allowed origins to list of regexes
|
||||||
origins_re = [r"^https?:\/\/%s(.*)" % o.replace('.', '\.') for o in app.config['CORS_ALLOW_ORIGINS']]
|
origins_re = [r"^https?:\/\/%s(.*)" % o.replace('.', '\.') for o in app.config['CORS_ALLOW_ORIGINS']]
|
||||||
cors = CORS(connexion_app.app, origins=origins_re)
|
cors = CORS(connexion_app.app, origins=origins_re)
|
||||||
|
|
||||||
|
print('=== USING THESE CONFIG SETTINGS: ===')
|
||||||
|
print('DB_HOST = ', )
|
||||||
|
print('CORS_ALLOW_ORIGINS = ', app.config['CORS_ALLOW_ORIGINS'])
|
||||||
|
print('DEVELOPMENT = ', app.config['DEVELOPMENT'])
|
||||||
|
print('TESTING = ', app.config['TESTING'])
|
||||||
|
print('PRODUCTION = ', app.config['PRODUCTION'])
|
||||||
|
print('PB_BASE_URL = ', app.config['PB_BASE_URL'])
|
||||||
|
print('LDAP_URL = ', app.config['LDAP_URL'])
|
||||||
|
print('APPLICATION_ROOT = ', app.config['APPLICATION_ROOT'])
|
||||||
|
print('PB_ENABLED = ', app.config['PB_ENABLED'])
|
||||||
|
|
||||||
@app.cli.command()
|
@app.cli.command()
|
||||||
def load_example_data():
|
def load_example_data():
|
||||||
|
@ -47,3 +57,11 @@ def load_example_data():
|
||||||
from example_data import ExampleDataLoader
|
from example_data import ExampleDataLoader
|
||||||
ExampleDataLoader.clean_db()
|
ExampleDataLoader.clean_db()
|
||||||
ExampleDataLoader().load_all()
|
ExampleDataLoader().load_all()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cli.command()
|
||||||
|
def load_example_rrt_data():
|
||||||
|
"""Load example data into the database."""
|
||||||
|
from example_data import ExampleDataLoader
|
||||||
|
ExampleDataLoader.clean_db()
|
||||||
|
ExampleDataLoader().load_rrt()
|
||||||
|
|
|
@ -56,7 +56,7 @@ paths:
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
- name: redirect_url
|
- name: redirect
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
|
|
|
@ -1,21 +1,32 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from flask import g
|
from flask import g
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from crc import session
|
from crc import session
|
||||||
from crc.api.common import ApiError, ApiErrorSchema
|
from crc.api.common import ApiError, ApiErrorSchema
|
||||||
from crc.models.protocol_builder import ProtocolBuilderStatus, ProtocolBuilderStudy
|
from crc.models.protocol_builder import ProtocolBuilderStatus
|
||||||
from crc.models.study import StudySchema, StudyFilesSchema, StudyModel, Study
|
from crc.models.study import StudySchema, StudyFilesSchema, StudyModel, Study
|
||||||
from crc.services.protocol_builder import ProtocolBuilderService
|
|
||||||
from crc.services.study_service import StudyService
|
from crc.services.study_service import StudyService
|
||||||
|
|
||||||
|
|
||||||
def add_study(body):
|
def add_study(body):
|
||||||
"""Or any study like object. """
|
"""Or any study like object. Body should include a title, and primary_investigator_id """
|
||||||
study: Study = StudySchema().load(body)
|
if 'primary_investigator_id' not in body:
|
||||||
study_model = StudyModel(**study.model_args())
|
raise ApiError("missing_pi", "Can't create a new study without a Primary Investigator.")
|
||||||
|
if 'title' not in body:
|
||||||
|
raise ApiError("missing_title", "Can't create a new study without a title.")
|
||||||
|
|
||||||
|
study_model = StudyModel(user_uid=g.user.uid,
|
||||||
|
title=body['title'],
|
||||||
|
primary_investigator_id=body['primary_investigator_id'],
|
||||||
|
last_updated=datetime.now(),
|
||||||
|
protocol_builder_status=ProtocolBuilderStatus.ACTIVE)
|
||||||
|
|
||||||
session.add(study_model)
|
session.add(study_model)
|
||||||
errors = StudyService._add_all_workflow_specs_to_study(study)
|
errors = StudyService._add_all_workflow_specs_to_study(study_model)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
study = StudyService().get_study(study_model.id)
|
||||||
study_data = StudySchema().dump(study)
|
study_data = StudySchema().dump(study)
|
||||||
study_data["errors"] = ApiErrorSchema(many=True).dump(errors)
|
study_data["errors"] = ApiErrorSchema(many=True).dump(errors)
|
||||||
return study_data
|
return study_data
|
||||||
|
@ -67,5 +78,3 @@ def all_studies_and_files():
|
||||||
studies = StudyService.get_studies_with_files()
|
studies = StudyService.get_studies_with_files()
|
||||||
results = StudyFilesSchema(many=True).dump(studies)
|
results = StudyFilesSchema(many=True).dump(studies)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ def verify_token(token):
|
||||||
def get_current_user():
|
def get_current_user():
|
||||||
return UserModelSchema().dump(g.user)
|
return UserModelSchema().dump(g.user)
|
||||||
|
|
||||||
@app.route('/login')
|
@app.route('/v1.0/login')
|
||||||
def sso_login():
|
def sso_login():
|
||||||
# This what I see coming back:
|
# This what I see coming back:
|
||||||
# X-Remote-Cn: Daniel Harold Funk (dhf8r)
|
# X-Remote-Cn: Daniel Harold Funk (dhf8r)
|
||||||
|
@ -126,7 +126,7 @@ def backdoor(
|
||||||
first_name=None,
|
first_name=None,
|
||||||
last_name=None,
|
last_name=None,
|
||||||
title=None,
|
title=None,
|
||||||
redirect_url=None,
|
redirect=None,
|
||||||
):
|
):
|
||||||
"""A backdoor for end-to-end system testing that allows the system to simulate logging in as a specific user.
|
"""A backdoor for end-to-end system testing that allows the system to simulate logging in as a specific user.
|
||||||
Only works if the application is running in a non-production environment.
|
Only works if the application is running in a non-production environment.
|
||||||
|
@ -149,9 +149,8 @@ def backdoor(
|
||||||
ApiError. If on production, returns a 404 error.
|
ApiError. If on production, returns a 404 error.
|
||||||
"""
|
"""
|
||||||
if not 'PRODUCTION' in app.config or not app.config['PRODUCTION']:
|
if not 'PRODUCTION' in app.config or not app.config['PRODUCTION']:
|
||||||
ldap_info = LdapUserInfo()
|
|
||||||
ldap_info.uid = connexion.request.args["uid"]
|
ldap_info = LdapService().user_info(uid)
|
||||||
ldap_info.email_address = connexion.request.args["email_address"]
|
return _handle_login(ldap_info, redirect)
|
||||||
return _handle_login(ldap_info, redirect_url)
|
|
||||||
else:
|
else:
|
||||||
raise ApiError('404', 'unknown')
|
raise ApiError('404', 'unknown')
|
||||||
|
|
|
@ -4,6 +4,7 @@ from crc import session
|
||||||
from crc.api.common import ApiError, ApiErrorSchema
|
from crc.api.common import ApiError, ApiErrorSchema
|
||||||
from crc.models.api_models import WorkflowApi, WorkflowApiSchema, NavigationItem, NavigationItemSchema
|
from crc.models.api_models import WorkflowApi, WorkflowApiSchema, NavigationItem, NavigationItemSchema
|
||||||
from crc.models.file import FileModel, LookupDataSchema
|
from crc.models.file import FileModel, LookupDataSchema
|
||||||
|
from crc.models.stats import TaskEventModel
|
||||||
from crc.models.workflow import WorkflowModel, WorkflowSpecModelSchema, WorkflowSpecModel, WorkflowSpecCategoryModel, \
|
from crc.models.workflow import WorkflowModel, WorkflowSpecModelSchema, WorkflowSpecModel, WorkflowSpecCategoryModel, \
|
||||||
WorkflowSpecCategoryModelSchema
|
WorkflowSpecCategoryModelSchema
|
||||||
from crc.services.file_service import FileService
|
from crc.services.file_service import FileService
|
||||||
|
@ -77,6 +78,8 @@ def delete_workflow_specification(spec_id):
|
||||||
for file in files:
|
for file in files:
|
||||||
FileService.delete_file(file.id)
|
FileService.delete_file(file.id)
|
||||||
|
|
||||||
|
session.query(TaskEventModel).filter(TaskEventModel.workflow_spec_id == spec_id).delete()
|
||||||
|
|
||||||
# Delete all stats and workflow models related to this specification
|
# Delete all stats and workflow models related to this specification
|
||||||
for workflow in session.query(WorkflowModel).filter_by(workflow_spec_id=spec_id):
|
for workflow in session.query(WorkflowModel).filter_by(workflow_spec_id=spec_id):
|
||||||
StudyService.delete_workflow(workflow)
|
StudyService.delete_workflow(workflow)
|
||||||
|
|
|
@ -35,7 +35,7 @@ class ApprovalModel(db.Model):
|
||||||
__tablename__ = 'approval'
|
__tablename__ = 'approval'
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
study_id = db.Column(db.Integer, db.ForeignKey(StudyModel.id), nullable=False)
|
study_id = db.Column(db.Integer, db.ForeignKey(StudyModel.id), nullable=False)
|
||||||
study = db.relationship(StudyModel)
|
study = db.relationship(StudyModel, backref='approval', cascade='all,delete')
|
||||||
workflow_id = db.Column(db.Integer, db.ForeignKey(WorkflowModel.id), nullable=False)
|
workflow_id = db.Column(db.Integer, db.ForeignKey(WorkflowModel.id), nullable=False)
|
||||||
workflow = db.relationship(WorkflowModel)
|
workflow = db.relationship(WorkflowModel)
|
||||||
approver_uid = db.Column(db.String) # Not linked to user model, as they may not have logged in yet.
|
approver_uid = db.Column(db.String) # Not linked to user model, as they may not have logged in yet.
|
||||||
|
|
|
@ -107,7 +107,8 @@ class CategorySchema(ma.Schema):
|
||||||
|
|
||||||
class Study(object):
|
class Study(object):
|
||||||
|
|
||||||
def __init__(self, id, title, last_updated, primary_investigator_id, user_uid,
|
def __init__(self, title, last_updated, primary_investigator_id, user_uid,
|
||||||
|
id=None,
|
||||||
protocol_builder_status=None,
|
protocol_builder_status=None,
|
||||||
sponsor="", hsr_number="", ind_number="", categories=[], **argsv):
|
sponsor="", hsr_number="", ind_number="", categories=[], **argsv):
|
||||||
self.id = id
|
self.id = id
|
||||||
|
@ -125,7 +126,8 @@ class Study(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_model(cls, study_model: StudyModel):
|
def from_model(cls, study_model: StudyModel):
|
||||||
args = {k: v for k, v in study_model.__dict__.items() if not k.startswith('_')}
|
id = study_model.id # Just read some value, in case the dict expired, otherwise dict may be empty.
|
||||||
|
args = dict((k, v) for k, v in study_model.__dict__.items() if not k.startswith('_'))
|
||||||
instance = cls(**args)
|
instance = cls(**args)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@ -144,10 +146,13 @@ class Study(object):
|
||||||
|
|
||||||
class StudySchema(ma.Schema):
|
class StudySchema(ma.Schema):
|
||||||
|
|
||||||
|
id = fields.Integer(required=False, allow_none=True)
|
||||||
categories = fields.List(fields.Nested(CategorySchema), dump_only=True)
|
categories = fields.List(fields.Nested(CategorySchema), dump_only=True)
|
||||||
warnings = fields.List(fields.Nested(ApiErrorSchema), dump_only=True)
|
warnings = fields.List(fields.Nested(ApiErrorSchema), dump_only=True)
|
||||||
protocol_builder_status = EnumField(ProtocolBuilderStatus)
|
protocol_builder_status = EnumField(ProtocolBuilderStatus)
|
||||||
hsr_number = fields.String(allow_none=True)
|
hsr_number = fields.String(allow_none=True)
|
||||||
|
sponsor = fields.String(allow_none=True)
|
||||||
|
ind_number = fields.String(allow_none=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Study
|
model = Study
|
||||||
|
|
|
@ -10,11 +10,11 @@ class RequestApproval(Script):
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
return """
|
return """
|
||||||
Creates an approval request on this workflow, by the given approver_uid(s),"
|
Creates an approval request on this workflow, by the given approver_uid(s),"
|
||||||
Takes one argument, which should point to data located in current task.
|
Takes multiple arguments, which should point to data located in current task
|
||||||
|
or be quoted strings.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
RequestApproval approver1 "dhf8r"
|
||||||
RequestApproval required_approvals.uids
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||||
|
@ -29,15 +29,16 @@ RequestApproval required_approvals.uids
|
||||||
ApprovalService.add_approval(study_id, workflow_id, id)
|
ApprovalService.add_approval(study_id, workflow_id, id)
|
||||||
|
|
||||||
def get_uids(self, task, args):
|
def get_uids(self, task, args):
|
||||||
if len(args) != 1:
|
if len(args) < 1:
|
||||||
raise ApiError(code="missing_argument",
|
raise ApiError(code="missing_argument",
|
||||||
message="The RequestApproval script requires 1 argument. The "
|
message="The RequestApproval script requires at least one argument. The "
|
||||||
"the name of the variable in the task data that contains user"
|
"the name of the variable in the task data that contains user"
|
||||||
"ids to process.")
|
"id to process. Multiple arguments are accepted.")
|
||||||
|
uids = []
|
||||||
uids = task.workflow.script_engine.evaluate(task, args[0])
|
for arg in args:
|
||||||
|
id = task.workflow.script_engine.evaluate_expression(task, arg)
|
||||||
if not isinstance(uids, str) and not isinstance(uids, list):
|
uids.append(id)
|
||||||
|
if not isinstance(id, str):
|
||||||
raise ApiError(code="invalid_argument",
|
raise ApiError(code="invalid_argument",
|
||||||
message="The RequestApproval script requires 1 argument. The "
|
message="The RequestApproval script requires 1 argument. The "
|
||||||
"the name of the variable in the task data that contains user"
|
"the name of the variable in the task data that contains user"
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from crc import db
|
||||||
|
from crc.api.common import ApiError
|
||||||
|
from crc.models.study import StudyModel
|
||||||
|
from crc.scripts.script import Script
|
||||||
|
|
||||||
|
|
||||||
|
class mock_study:
|
||||||
|
def __init__(self):
|
||||||
|
self.title = ""
|
||||||
|
self.principle_investigator_id = ""
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateStudy(Script):
|
||||||
|
|
||||||
|
argument_error_message = "You must supply at least one argument to the " \
|
||||||
|
"update_study task, in the form [study_field]:[value]",
|
||||||
|
|
||||||
|
def get_description(self):
|
||||||
|
return """
|
||||||
|
Allows you to set specific attributes on the Study model by mapping them to
|
||||||
|
values in the task data. Should be called with the value to set (either title, or pi)
|
||||||
|
followed by a ":" and then the value to use in dot notation.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
UpdateStudy title:PIComputingID.label pi:PIComputingID.value
|
||||||
|
"""
|
||||||
|
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||||
|
study = mock_study
|
||||||
|
self.__update_study(task, study, *args)
|
||||||
|
|
||||||
|
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
|
||||||
|
study = db.session.query(StudyModel).filter(StudyModel.id == study_id).first()
|
||||||
|
self.__update_study(task, study, *args)
|
||||||
|
db.session.add(study)
|
||||||
|
|
||||||
|
def __update_study(self, task, study, *args):
|
||||||
|
if len(args) < 1:
|
||||||
|
raise ApiError.from_task("missing_argument", self.argument_error_message,
|
||||||
|
task=task)
|
||||||
|
|
||||||
|
for arg in args:
|
||||||
|
try:
|
||||||
|
field, value_lookup = arg.split(':')
|
||||||
|
except:
|
||||||
|
raise ApiError.from_task("invalid_argument", self.argument_error_message,
|
||||||
|
task=task)
|
||||||
|
|
||||||
|
value = task.workflow.script_engine.evaluate_expression(task, value_lookup)
|
||||||
|
|
||||||
|
if field.lower() == "title":
|
||||||
|
study.title = value
|
||||||
|
elif field.lower() == "pi":
|
||||||
|
study.primary_investigator_id = value
|
||||||
|
else:
|
||||||
|
raise ApiError.from_task("invalid_argument", self.argument_error_message,
|
||||||
|
task=task)
|
|
@ -133,7 +133,6 @@ class FileService(object):
|
||||||
return file_extension.lower().strip()[1:]
|
return file_extension.lower().strip()[1:]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
||||||
def update_file(file_model, binary_data, content_type):
|
def update_file(file_model, binary_data, content_type):
|
||||||
session.flush() # Assure the database is up-to-date before running this.
|
session.flush() # Assure the database is up-to-date before running this.
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,10 @@ class LdapUserInfo(object):
|
||||||
|
|
||||||
class LdapService(object):
|
class LdapService(object):
|
||||||
search_base = "ou=People,o=University of Virginia,c=US"
|
search_base = "ou=People,o=University of Virginia,c=US"
|
||||||
attributes = ['uid', 'cn', 'displayName', 'givenName', 'mail', 'objectClass', 'UvaDisplayDepartment',
|
attributes = ['uid', 'cn', 'sn', 'displayName', 'givenName', 'mail', 'objectClass', 'UvaDisplayDepartment',
|
||||||
'telephoneNumber', 'title', 'uvaPersonIAMAffiliation', 'uvaPersonSponsoredType']
|
'telephoneNumber', 'title', 'uvaPersonIAMAffiliation', 'uvaPersonSponsoredType']
|
||||||
uid_search_string = "(&(objectclass=person)(uid=%s))"
|
uid_search_string = "(&(objectclass=person)(uid=%s))"
|
||||||
|
user_or_last_name_search_string = "(&(objectclass=person)(|(uid=%s*)(sn=%s*)))"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
if app.config['TESTING']:
|
if app.config['TESTING']:
|
||||||
|
@ -66,7 +67,8 @@ class LdapService(object):
|
||||||
return LdapUserInfo.from_entry(entry)
|
return LdapUserInfo.from_entry(entry)
|
||||||
|
|
||||||
def search_users(self, query, limit):
|
def search_users(self, query, limit):
|
||||||
search_string = LdapService.uid_search_string % query
|
if len(query) < 3: return []
|
||||||
|
search_string = LdapService.user_or_last_name_search_string % (query, query)
|
||||||
self.conn.search(LdapService.search_base, search_string, attributes=LdapService.attributes)
|
self.conn.search(LdapService.search_base, search_string, attributes=LdapService.attributes)
|
||||||
|
|
||||||
# Entries are returned as a generator, accessing entries
|
# Entries are returned as a generator, accessing entries
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
from pandas import ExcelFile
|
from pandas import ExcelFile
|
||||||
|
from sqlalchemy import func, desc
|
||||||
|
from sqlalchemy.sql.functions import GenericFunction
|
||||||
|
|
||||||
from crc import db
|
from crc import db
|
||||||
from crc.api.common import ApiError
|
from crc.api.common import ApiError
|
||||||
|
@ -7,6 +11,9 @@ from crc.models.file import FileDataModel, LookupFileModel, LookupDataModel
|
||||||
from crc.services.file_service import FileService
|
from crc.services.file_service import FileService
|
||||||
from crc.services.ldap_service import LdapService
|
from crc.services.ldap_service import LdapService
|
||||||
|
|
||||||
|
class TSRank(GenericFunction):
|
||||||
|
package = 'full_text'
|
||||||
|
name = 'ts_rank'
|
||||||
|
|
||||||
class LookupService(object):
|
class LookupService(object):
|
||||||
|
|
||||||
|
@ -112,20 +119,31 @@ class LookupService(object):
|
||||||
db_query = LookupDataModel.query.filter(LookupDataModel.lookup_file_model == lookup_file_model)
|
db_query = LookupDataModel.query.filter(LookupDataModel.lookup_file_model == lookup_file_model)
|
||||||
|
|
||||||
query = query.strip()
|
query = query.strip()
|
||||||
if len(query) > 1:
|
if len(query) > 0:
|
||||||
if ' ' in query:
|
if ' ' in query:
|
||||||
terms = query.split(' ')
|
terms = query.split(' ')
|
||||||
new_terms = []
|
new_terms = ["'%s'" % query]
|
||||||
for t in terms:
|
for t in terms:
|
||||||
new_terms.append(t + ":*")
|
new_terms.append("%s:*" % t)
|
||||||
query = '|'.join(new_terms)
|
new_query = ' | '.join(new_terms)
|
||||||
else:
|
else:
|
||||||
query = "%s:*" % query
|
new_query = "%s:*" % query
|
||||||
db_query = db_query.filter(LookupDataModel.label.match(query))
|
|
||||||
|
|
||||||
# db_query = db_query.filter(text("lookup_data.label @@ to_tsquery('simple', '%s')" % query))
|
# Run the full text query
|
||||||
|
db_query = db_query.filter(LookupDataModel.label.match(new_query))
|
||||||
|
# But hackishly order by like, which does a good job of
|
||||||
|
# pulling more relevant matches to the top.
|
||||||
|
db_query = db_query.order_by(desc(LookupDataModel.label.like("%" + query + "%")))
|
||||||
|
#ORDER BY name LIKE concat('%', ticker, '%') desc, rank DESC
|
||||||
|
|
||||||
return db_query.limit(limit).all()
|
# db_query = db_query.order_by(desc(func.full_text.ts_rank(
|
||||||
|
# func.to_tsvector(LookupDataModel.label),
|
||||||
|
# func.to_tsquery(query))))
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
||||||
|
result = db_query.limit(limit).all()
|
||||||
|
logging.getLogger('sqlalchemy.engine').setLevel(logging.ERROR)
|
||||||
|
return result
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _run_ldap_query(query, limit):
|
def _run_ldap_query(query, limit):
|
||||||
|
|
|
@ -5,17 +5,22 @@ import requests
|
||||||
|
|
||||||
from crc import app
|
from crc import app
|
||||||
from crc.api.common import ApiError
|
from crc.api.common import ApiError
|
||||||
from crc.models.protocol_builder import ProtocolBuilderStudy, ProtocolBuilderStudySchema, ProtocolBuilderInvestigator, \
|
from crc.models.protocol_builder import ProtocolBuilderStudySchema, ProtocolBuilderRequiredDocument
|
||||||
ProtocolBuilderRequiredDocument, ProtocolBuilderRequiredDocumentSchema
|
|
||||||
|
|
||||||
|
|
||||||
class ProtocolBuilderService(object):
|
class ProtocolBuilderService(object):
|
||||||
ENABLED = app.config['PB_ENABLED']
|
|
||||||
STUDY_URL = app.config['PB_USER_STUDIES_URL']
|
STUDY_URL = app.config['PB_USER_STUDIES_URL']
|
||||||
INVESTIGATOR_URL = app.config['PB_INVESTIGATORS_URL']
|
INVESTIGATOR_URL = app.config['PB_INVESTIGATORS_URL']
|
||||||
REQUIRED_DOCS_URL = app.config['PB_REQUIRED_DOCS_URL']
|
REQUIRED_DOCS_URL = app.config['PB_REQUIRED_DOCS_URL']
|
||||||
STUDY_DETAILS_URL = app.config['PB_STUDY_DETAILS_URL']
|
STUDY_DETAILS_URL = app.config['PB_STUDY_DETAILS_URL']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_enabled():
|
||||||
|
if isinstance(app.config['PB_ENABLED'], str):
|
||||||
|
return app.config['PB_ENABLED'].lower() == "true"
|
||||||
|
else:
|
||||||
|
return app.config['PB_ENABLED'] is True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_studies(user_id) -> {}:
|
def get_studies(user_id) -> {}:
|
||||||
ProtocolBuilderService.__enabled_or_raise()
|
ProtocolBuilderService.__enabled_or_raise()
|
||||||
|
@ -44,7 +49,7 @@ class ProtocolBuilderService(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __enabled_or_raise():
|
def __enabled_or_raise():
|
||||||
if not ProtocolBuilderService.ENABLED:
|
if not ProtocolBuilderService.is_enabled():
|
||||||
raise ApiError("protocol_builder_disabled", "The Protocol Builder Service is currently disabled.")
|
raise ApiError("protocol_builder_disabled", "The Protocol Builder Service is currently disabled.")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -60,4 +65,3 @@ class ProtocolBuilderService(object):
|
||||||
"Received an invalid response from the protocol builder (status %s): %s when calling "
|
"Received an invalid response from the protocol builder (status %s): %s when calling "
|
||||||
"url '%s'." %
|
"url '%s'." %
|
||||||
(response.status_code, response.text, url))
|
(response.status_code, response.text, url))
|
||||||
|
|
||||||
|
|
|
@ -64,16 +64,16 @@ class StudyService(object):
|
||||||
def delete_study(study_id):
|
def delete_study(study_id):
|
||||||
session.query(TaskEventModel).filter_by(study_id=study_id).delete()
|
session.query(TaskEventModel).filter_by(study_id=study_id).delete()
|
||||||
for workflow in session.query(WorkflowModel).filter_by(study_id=study_id):
|
for workflow in session.query(WorkflowModel).filter_by(study_id=study_id):
|
||||||
StudyService.delete_workflow(workflow.id)
|
StudyService.delete_workflow(workflow)
|
||||||
session.query(StudyModel).filter_by(id=study_id).delete()
|
session.query(StudyModel).filter_by(id=study_id).delete()
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_workflow(workflow_id):
|
def delete_workflow(workflow):
|
||||||
for file in session.query(FileModel).filter_by(workflow_id=workflow_id).all():
|
for file in session.query(FileModel).filter_by(workflow_id=workflow.id).all():
|
||||||
FileService.delete_file(file.id)
|
FileService.delete_file(file.id)
|
||||||
session.query(TaskEventModel).filter_by(workflow_id=workflow_id).delete()
|
session.query(TaskEventModel).filter_by(workflow_id=workflow.id).delete()
|
||||||
session.query(WorkflowModel).filter_by(id=workflow_id).delete()
|
session.query(WorkflowModel).filter_by(id=workflow.id).delete()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_categories():
|
def get_categories():
|
||||||
|
@ -117,7 +117,7 @@ class StudyService(object):
|
||||||
that is available.."""
|
that is available.."""
|
||||||
|
|
||||||
# Get PB required docs, if Protocol Builder Service is enabled.
|
# Get PB required docs, if Protocol Builder Service is enabled.
|
||||||
if ProtocolBuilderService.ENABLED:
|
if ProtocolBuilderService.is_enabled():
|
||||||
try:
|
try:
|
||||||
pb_docs = ProtocolBuilderService.get_required_docs(study_id=study_id)
|
pb_docs = ProtocolBuilderService.get_required_docs(study_id=study_id)
|
||||||
except requests.exceptions.ConnectionError as ce:
|
except requests.exceptions.ConnectionError as ce:
|
||||||
|
@ -133,7 +133,7 @@ class StudyService(object):
|
||||||
documents = {}
|
documents = {}
|
||||||
for code, doc in doc_dictionary.items():
|
for code, doc in doc_dictionary.items():
|
||||||
|
|
||||||
if ProtocolBuilderService.ENABLED:
|
if ProtocolBuilderService.is_enabled():
|
||||||
pb_data = next((item for item in pb_docs if int(item['AUXDOCID']) == int(doc['id'])), None)
|
pb_data = next((item for item in pb_docs if int(item['AUXDOCID']) == int(doc['id'])), None)
|
||||||
doc['required'] = False
|
doc['required'] = False
|
||||||
if pb_data:
|
if pb_data:
|
||||||
|
@ -216,8 +216,10 @@ class StudyService(object):
|
||||||
"""Assures that the studies we have locally for the given user are
|
"""Assures that the studies we have locally for the given user are
|
||||||
in sync with the studies available in protocol builder. """
|
in sync with the studies available in protocol builder. """
|
||||||
|
|
||||||
if not ProtocolBuilderService.ENABLED:
|
if ProtocolBuilderService.is_enabled():
|
||||||
return
|
|
||||||
|
app.logger.info("The Protocol Builder is enabled. app.config['PB_ENABLED'] = " +
|
||||||
|
str(app.config['PB_ENABLED']))
|
||||||
|
|
||||||
# Get studies matching this user from Protocol Builder
|
# Get studies matching this user from Protocol Builder
|
||||||
pb_studies: List[ProtocolBuilderStudy] = ProtocolBuilderService.get_studies(user.uid)
|
pb_studies: List[ProtocolBuilderStudy] = ProtocolBuilderService.get_studies(user.uid)
|
||||||
|
@ -290,8 +292,8 @@ class StudyService(object):
|
||||||
return WorkflowProcessor.run_master_spec(master_specs[0], study_model)
|
return WorkflowProcessor.run_master_spec(master_specs[0], study_model)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _add_all_workflow_specs_to_study(study):
|
def _add_all_workflow_specs_to_study(study_model:StudyModel):
|
||||||
existing_models = session.query(WorkflowModel).filter(WorkflowModel.study_id == study.id).all()
|
existing_models = session.query(WorkflowModel).filter(WorkflowModel.study == study_model).all()
|
||||||
existing_specs = list(m.workflow_spec_id for m in existing_models)
|
existing_specs = list(m.workflow_spec_id for m in existing_models)
|
||||||
new_specs = session.query(WorkflowSpecModel). \
|
new_specs = session.query(WorkflowSpecModel). \
|
||||||
filter(WorkflowSpecModel.is_master_spec == False). \
|
filter(WorkflowSpecModel.is_master_spec == False). \
|
||||||
|
@ -300,15 +302,15 @@ class StudyService(object):
|
||||||
errors = []
|
errors = []
|
||||||
for workflow_spec in new_specs:
|
for workflow_spec in new_specs:
|
||||||
try:
|
try:
|
||||||
StudyService._create_workflow_model(study, workflow_spec)
|
StudyService._create_workflow_model(study_model, workflow_spec)
|
||||||
except WorkflowException as we:
|
except WorkflowException as we:
|
||||||
errors.append(ApiError.from_task_spec("workflow_execution_exception", str(we), we.sender))
|
errors.append(ApiError.from_task_spec("workflow_execution_exception", str(we), we.sender))
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_workflow_model(study, spec):
|
def _create_workflow_model(study: StudyModel, spec):
|
||||||
workflow_model = WorkflowModel(status=WorkflowStatus.not_started,
|
workflow_model = WorkflowModel(status=WorkflowStatus.not_started,
|
||||||
study_id=study.id,
|
study=study,
|
||||||
workflow_spec_id=spec.id,
|
workflow_spec_id=spec.id,
|
||||||
last_updated=datetime.now())
|
last_updated=datetime.now())
|
||||||
session.add(workflow_model)
|
session.add(workflow_model)
|
||||||
|
|
|
@ -70,6 +70,14 @@ class CustomBpmnScriptEngine(BpmnScriptEngine):
|
||||||
"Unable to locate Script: '%s:%s'" % (module_name, class_name),
|
"Unable to locate Script: '%s:%s'" % (module_name, class_name),
|
||||||
task=task)
|
task=task)
|
||||||
|
|
||||||
|
def evaluate_expression(self, task, expression):
|
||||||
|
"""
|
||||||
|
Evaluate the given expression, within the context of the given task and
|
||||||
|
return the result.
|
||||||
|
"""
|
||||||
|
exp,valid = self.validateExpression(expression)
|
||||||
|
return self._eval(exp, **task.data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def camel_to_snake(camel):
|
def camel_to_snake(camel):
|
||||||
camel = camel.strip()
|
camel = camel.strip()
|
||||||
|
@ -276,43 +284,6 @@ class WorkflowProcessor(object):
|
||||||
tag=ve.tag)
|
tag=ve.tag)
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def populate_form_with_random_data(task, task_api):
|
|
||||||
"""populates a task with random data - useful for testing a spec."""
|
|
||||||
|
|
||||||
if not hasattr(task.task_spec, 'form'): return
|
|
||||||
|
|
||||||
form_data = {}
|
|
||||||
for field in task_api.form.fields:
|
|
||||||
if field.type == "enum":
|
|
||||||
if len(field.options) > 0:
|
|
||||||
form_data[field.id] = random.choice(field.options)
|
|
||||||
else:
|
|
||||||
raise ApiError.from_task("invalid_enum", "You specified an enumeration field (%s),"
|
|
||||||
" with no options" % field.id,
|
|
||||||
task)
|
|
||||||
elif field.type == "long":
|
|
||||||
form_data[field.id] = random.randint(1, 1000)
|
|
||||||
elif field.type == 'boolean':
|
|
||||||
form_data[field.id] = random.choice([True, False])
|
|
||||||
elif field.type == 'file':
|
|
||||||
form_data[field.id] = random.randint(1, 100)
|
|
||||||
elif field.type == 'files':
|
|
||||||
form_data[field.id] = random.randrange(1, 100)
|
|
||||||
else:
|
|
||||||
form_data[field.id] = WorkflowProcessor._random_string()
|
|
||||||
if task.data is None:
|
|
||||||
task.data = {}
|
|
||||||
task.data.update(form_data)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _random_string(string_length=10):
|
|
||||||
"""Generate a random string of fixed length """
|
|
||||||
letters = string.ascii_lowercase
|
|
||||||
return ''.join(random.choice(letters) for i in range(string_length))
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def status_of(bpmn_workflow):
|
def status_of(bpmn_workflow):
|
||||||
if bpmn_workflow.is_completed():
|
if bpmn_workflow.is_completed():
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import string
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import random
|
||||||
|
|
||||||
import jinja2
|
import jinja2
|
||||||
from SpiffWorkflow import Task as SpiffTask, WorkflowException
|
from SpiffWorkflow import Task as SpiffTask, WorkflowException
|
||||||
|
@ -53,12 +55,82 @@ class WorkflowService(object):
|
||||||
task_api = WorkflowService.spiff_task_to_api_task(
|
task_api = WorkflowService.spiff_task_to_api_task(
|
||||||
task,
|
task,
|
||||||
add_docs_and_forms=True) # Assure we try to process the documenation, and raise those errors.
|
add_docs_and_forms=True) # Assure we try to process the documenation, and raise those errors.
|
||||||
WorkflowProcessor.populate_form_with_random_data(task, task_api)
|
WorkflowService.populate_form_with_random_data(task, task_api)
|
||||||
task.complete()
|
task.complete()
|
||||||
except WorkflowException as we:
|
except WorkflowException as we:
|
||||||
raise ApiError.from_task_spec("workflow_execution_exception", str(we),
|
raise ApiError.from_task_spec("workflow_execution_exception", str(we),
|
||||||
we.sender)
|
we.sender)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def populate_form_with_random_data(task, task_api):
|
||||||
|
"""populates a task with random data - useful for testing a spec."""
|
||||||
|
|
||||||
|
if not hasattr(task.task_spec, 'form'): return
|
||||||
|
|
||||||
|
form_data = {}
|
||||||
|
for field in task_api.form.fields:
|
||||||
|
if field.type == "enum":
|
||||||
|
if len(field.options) > 0:
|
||||||
|
random_choice = random.choice(field.options)
|
||||||
|
if isinstance(random_choice, dict):
|
||||||
|
form_data[field.id] = random.choice(field.options)['id']
|
||||||
|
else:
|
||||||
|
# fixme: why it is sometimes an EnumFormFieldOption, and other times not?
|
||||||
|
form_data[field.id] = random_choice.id ## Assume it is an EnumFormFieldOption
|
||||||
|
else:
|
||||||
|
raise ApiError.from_task("invalid_enum", "You specified an enumeration field (%s),"
|
||||||
|
" with no options" % field.id,
|
||||||
|
task)
|
||||||
|
elif field.type == "autocomplete":
|
||||||
|
lookup_model = LookupService.get_lookup_table(task, field)
|
||||||
|
if field.has_property(Task.PROP_LDAP_LOOKUP):
|
||||||
|
form_data[field.id] = {
|
||||||
|
"label": "dhf8r",
|
||||||
|
"value": "Dan Funk",
|
||||||
|
"data": {
|
||||||
|
"uid": "dhf8r",
|
||||||
|
"display_name": "Dan Funk",
|
||||||
|
"given_name": "Dan",
|
||||||
|
"email_address": "dhf8r@virginia.edu",
|
||||||
|
"department": "Depertment of Psychocosmographictology",
|
||||||
|
"affiliation": "Rousabout",
|
||||||
|
"sponsor_type": "Staff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elif lookup_model:
|
||||||
|
data = db.session.query(LookupDataModel).filter(
|
||||||
|
LookupDataModel.lookup_file_model == lookup_model).limit(10).all()
|
||||||
|
options = []
|
||||||
|
for d in data:
|
||||||
|
options.append({"id": d.value, "name": d.label})
|
||||||
|
form_data[field.id] = random.choice(options)
|
||||||
|
else:
|
||||||
|
raise ApiError.from_task("invalid_autocomplete", "The settings for this auto complete field "
|
||||||
|
"are incorrect: %s " % field.id, task)
|
||||||
|
elif field.type == "long":
|
||||||
|
form_data[field.id] = random.randint(1, 1000)
|
||||||
|
elif field.type == 'boolean':
|
||||||
|
form_data[field.id] = random.choice([True, False])
|
||||||
|
elif field.type == 'file':
|
||||||
|
form_data[field.id] = random.randint(1, 100)
|
||||||
|
elif field.type == 'files':
|
||||||
|
form_data[field.id] = random.randrange(1, 100)
|
||||||
|
else:
|
||||||
|
form_data[field.id] = WorkflowService._random_string()
|
||||||
|
if task.data is None:
|
||||||
|
task.data = {}
|
||||||
|
task.data.update(form_data)
|
||||||
|
|
||||||
|
def __get_options(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _random_string(string_length=10):
|
||||||
|
"""Generate a random string of fixed length """
|
||||||
|
letters = string.ascii_lowercase
|
||||||
|
return ''.join(random.choice(letters) for i in range(string_length))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def spiff_task_to_api_task(spiff_task, add_docs_and_forms=False):
|
def spiff_task_to_api_task(spiff_task, add_docs_and_forms=False):
|
||||||
task_type = spiff_task.task_spec.__class__.__name__
|
task_type = spiff_task.task_spec.__class__.__name__
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_0be39yr" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_0be39yr" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||||
<bpmn:process id="Process_1cme33c" isExecutable="false">
|
<bpmn:process id="Process_1cme33c" isExecutable="false">
|
||||||
<bpmn:parallelGateway id="ParallelGateway_0ecwf3g">
|
<bpmn:parallelGateway id="ParallelGateway_0ecwf3g">
|
||||||
<bpmn:incoming>Flow_1wqp7vf</bpmn:incoming>
|
<bpmn:incoming>Flow_1wqp7vf</bpmn:incoming>
|
||||||
|
@ -36,10 +36,9 @@
|
||||||
</camunda:formField>
|
</camunda:formField>
|
||||||
<camunda:formField id="ProtocolOwnerName" label="Protocol Owner Name" type="autocomplete">
|
<camunda:formField id="ProtocolOwnerName" label="Protocol Owner Name" type="autocomplete">
|
||||||
<camunda:properties>
|
<camunda:properties>
|
||||||
<camunda:property id="enum.options.file" value="SponsorList.xls" />
|
<camunda:property id="spreadsheet.name" value="SponsorList.xls" />
|
||||||
<camunda:property id="enum.options.value.column" value="CUSTOMER_NUMBER" />
|
<camunda:property id="spreadsheet.value.column" value="CUSTOMER_NUMBER" />
|
||||||
<camunda:property id="enum.options.label.column" value="CUSTOMER_NAME" />
|
<camunda:property id="spreadsheet.label.column" value="CUSTOMER_NAME" />
|
||||||
<camunda:property id="enum.options.lookup" value="True" />
|
|
||||||
<camunda:property id="help" value="#### How To:\nYou can find the name by typing any part (at least 3 characters) of the name.\n\nNote: This source of this list is in the Integration System (Oracle) and the information is owned by and managed by the OSP team.\n\nIf you are not finding the name or need to make any changes.\n1. Email 'Information Team listserve' osp-infoteam@virginia.edu with the Subject Line "Requesting New Sponsor Setup" and provide the following information:\n - Sponsor Legal Name, Address, Sponsor Classification (Federal Government, Foreign Entity, Foundation, Industry, Local Government, Other Colleges & Universities or State Government) as stated in the agreement/notification.\n - Copies of the agreement from the sponsor (contract, award letter, email, etc.).\n2. Once all the required information is received, OSP will add the name to the list.\n3. The updated list should be available for your selection in the workflow within 2 business days." />
|
<camunda:property id="help" value="#### How To:\nYou can find the name by typing any part (at least 3 characters) of the name.\n\nNote: This source of this list is in the Integration System (Oracle) and the information is owned by and managed by the OSP team.\n\nIf you are not finding the name or need to make any changes.\n1. Email 'Information Team listserve' osp-infoteam@virginia.edu with the Subject Line "Requesting New Sponsor Setup" and provide the following information:\n - Sponsor Legal Name, Address, Sponsor Classification (Federal Government, Foreign Entity, Foundation, Industry, Local Government, Other Colleges & Universities or State Government) as stated in the agreement/notification.\n - Copies of the agreement from the sponsor (contract, award letter, email, etc.).\n2. Once all the required information is received, OSP will add the name to the list.\n3. The updated list should be available for your selection in the workflow within 2 business days." />
|
||||||
<camunda:property id="description" value="The protocol owner name is always an entity. For example, if this is a UVA Primary Investigator - Investigator initiated study, the Protocol Owner Name will be "University of Virginia"" />
|
<camunda:property id="description" value="The protocol owner name is always an entity. For example, if this is a UVA Primary Investigator - Investigator initiated study, the Protocol Owner Name will be "University of Virginia"" />
|
||||||
</camunda:properties>
|
</camunda:properties>
|
||||||
|
@ -235,104 +234,104 @@
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1cme33c">
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1cme33c">
|
||||||
<bpmndi:BPMNEdge id="Flow_1wqp7vf_di" bpmnElement="Flow_1wqp7vf">
|
<bpmndi:BPMNEdge id="Flow_1wqp7vf_di" bpmnElement="Flow_1wqp7vf">
|
||||||
<di:waypoint x="450" y="325" />
|
<di:waypoint x="820" y="325" />
|
||||||
<di:waypoint x="495" y="325" />
|
<di:waypoint x="865" y="325" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_1tfyk5m_di" bpmnElement="Flow_1tfyk5m">
|
<bpmndi:BPMNEdge id="Flow_1tfyk5m_di" bpmnElement="Flow_1tfyk5m">
|
||||||
<di:waypoint x="300" y="325" />
|
<di:waypoint x="670" y="325" />
|
||||||
<di:waypoint x="350" y="325" />
|
<di:waypoint x="720" y="325" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_1d4dncx_di" bpmnElement="Flow_1d4dncx">
|
<bpmndi:BPMNEdge id="Flow_1d4dncx_di" bpmnElement="Flow_1d4dncx">
|
||||||
<di:waypoint x="520" y="300" />
|
<di:waypoint x="890" y="300" />
|
||||||
<di:waypoint x="520" y="250" />
|
<di:waypoint x="890" y="250" />
|
||||||
<di:waypoint x="620" y="250" />
|
<di:waypoint x="990" y="250" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_16v64sg_di" bpmnElement="Flow_16v64sg">
|
<bpmndi:BPMNEdge id="Flow_16v64sg_di" bpmnElement="Flow_16v64sg">
|
||||||
<di:waypoint x="140" y="325" />
|
<di:waypoint x="510" y="325" />
|
||||||
<di:waypoint x="200" y="325" />
|
<di:waypoint x="570" y="325" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_09h1imz_di" bpmnElement="Flow_09h1imz">
|
<bpmndi:BPMNEdge id="Flow_09h1imz_di" bpmnElement="Flow_09h1imz">
|
||||||
<di:waypoint x="-20" y="325" />
|
<di:waypoint x="350" y="325" />
|
||||||
<di:waypoint x="40" y="325" />
|
<di:waypoint x="410" y="325" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_1v7oplk_di" bpmnElement="SequenceFlow_1v7oplk">
|
<bpmndi:BPMNEdge id="SequenceFlow_1v7oplk_di" bpmnElement="SequenceFlow_1v7oplk">
|
||||||
<di:waypoint x="845" y="325" />
|
<di:waypoint x="1215" y="325" />
|
||||||
<di:waypoint x="898" y="325" />
|
<di:waypoint x="1268" y="325" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_0rw17h2_di" bpmnElement="SequenceFlow_0rw17h2">
|
<bpmndi:BPMNEdge id="SequenceFlow_0rw17h2_di" bpmnElement="SequenceFlow_0rw17h2">
|
||||||
<di:waypoint x="720" y="500" />
|
<di:waypoint x="1090" y="500" />
|
||||||
<di:waypoint x="820" y="500" />
|
<di:waypoint x="1190" y="500" />
|
||||||
<di:waypoint x="820" y="350" />
|
<di:waypoint x="1190" y="350" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_0gsy7mo_di" bpmnElement="SequenceFlow_0gsy7mo">
|
<bpmndi:BPMNEdge id="SequenceFlow_0gsy7mo_di" bpmnElement="SequenceFlow_0gsy7mo">
|
||||||
<di:waypoint x="720" y="380" />
|
<di:waypoint x="1090" y="380" />
|
||||||
<di:waypoint x="820" y="380" />
|
<di:waypoint x="1190" y="380" />
|
||||||
<di:waypoint x="820" y="350" />
|
<di:waypoint x="1190" y="350" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_1o39rt4_di" bpmnElement="SequenceFlow_1o39rt4">
|
<bpmndi:BPMNEdge id="SequenceFlow_1o39rt4_di" bpmnElement="SequenceFlow_1o39rt4">
|
||||||
<di:waypoint x="720" y="250" />
|
<di:waypoint x="1090" y="250" />
|
||||||
<di:waypoint x="820" y="250" />
|
<di:waypoint x="1190" y="250" />
|
||||||
<di:waypoint x="820" y="300" />
|
<di:waypoint x="1190" y="300" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_02nbqkn_di" bpmnElement="SequenceFlow_02nbqkn">
|
<bpmndi:BPMNEdge id="SequenceFlow_02nbqkn_di" bpmnElement="SequenceFlow_02nbqkn">
|
||||||
<di:waypoint x="720" y="130" />
|
<di:waypoint x="1090" y="130" />
|
||||||
<di:waypoint x="820" y="130" />
|
<di:waypoint x="1190" y="130" />
|
||||||
<di:waypoint x="820" y="300" />
|
<di:waypoint x="1190" y="300" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_0xj8i4c_di" bpmnElement="SequenceFlow_0xj8i4c">
|
<bpmndi:BPMNEdge id="SequenceFlow_0xj8i4c_di" bpmnElement="SequenceFlow_0xj8i4c">
|
||||||
<di:waypoint x="520" y="350" />
|
<di:waypoint x="890" y="350" />
|
||||||
<di:waypoint x="520" y="500" />
|
<di:waypoint x="890" y="500" />
|
||||||
<di:waypoint x="620" y="500" />
|
<di:waypoint x="990" y="500" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_1idbomg_di" bpmnElement="SequenceFlow_1idbomg">
|
<bpmndi:BPMNEdge id="SequenceFlow_1idbomg_di" bpmnElement="SequenceFlow_1idbomg">
|
||||||
<di:waypoint x="520" y="350" />
|
<di:waypoint x="890" y="350" />
|
||||||
<di:waypoint x="520" y="380" />
|
<di:waypoint x="890" y="380" />
|
||||||
<di:waypoint x="620" y="380" />
|
<di:waypoint x="990" y="380" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_0f61fxp_di" bpmnElement="SequenceFlow_0f61fxp">
|
<bpmndi:BPMNEdge id="SequenceFlow_0f61fxp_di" bpmnElement="SequenceFlow_0f61fxp">
|
||||||
<di:waypoint x="520" y="300" />
|
<di:waypoint x="890" y="300" />
|
||||||
<di:waypoint x="520" y="130" />
|
<di:waypoint x="890" y="130" />
|
||||||
<di:waypoint x="620" y="130" />
|
<di:waypoint x="990" y="130" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_1r3yrhy_di" bpmnElement="SequenceFlow_1r3yrhy">
|
<bpmndi:BPMNEdge id="SequenceFlow_1r3yrhy_di" bpmnElement="SequenceFlow_1r3yrhy">
|
||||||
<di:waypoint x="-182" y="325" />
|
<di:waypoint x="188" y="325" />
|
||||||
<di:waypoint x="-120" y="325" />
|
<di:waypoint x="250" y="325" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape id="ParallelGateway_0ecwf3g_di" bpmnElement="ParallelGateway_0ecwf3g">
|
<bpmndi:BPMNShape id="ParallelGateway_0ecwf3g_di" bpmnElement="ParallelGateway_0ecwf3g">
|
||||||
<dc:Bounds x="495" y="300" width="50" height="50" />
|
<dc:Bounds x="865" y="300" width="50" height="50" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="ParallelGateway_01234ff_di" bpmnElement="ParallelGateway_01234ff">
|
<bpmndi:BPMNShape id="ParallelGateway_01234ff_di" bpmnElement="ParallelGateway_01234ff">
|
||||||
<dc:Bounds x="795" y="300" width="50" height="50" />
|
<dc:Bounds x="1165" y="300" width="50" height="50" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="EndEvent_16uwhzg_di" bpmnElement="EndEvent_16uwhzg">
|
<bpmndi:BPMNShape id="EndEvent_16uwhzg_di" bpmnElement="EndEvent_16uwhzg">
|
||||||
<dc:Bounds x="898" y="307" width="36" height="36" />
|
<dc:Bounds x="1268" y="307" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="StartEvent_1mhzkcr_di" bpmnElement="StartEvent_1mhzkcr">
|
<bpmndi:BPMNShape id="StartEvent_1mhzkcr_di" bpmnElement="StartEvent_1mhzkcr">
|
||||||
<dc:Bounds x="-218" y="307" width="36" height="36" />
|
<dc:Bounds x="152" y="307" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="UserTask_1y1qon7_di" bpmnElement="UserTask_1y1qon7">
|
<bpmndi:BPMNShape id="UserTask_1y1qon7_di" bpmnElement="UserTask_1y1qon7">
|
||||||
<dc:Bounds x="620" y="340" width="100" height="80" />
|
<dc:Bounds x="990" y="340" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="UserTask_01zzzg9_di" bpmnElement="UserTask_01zzzg9">
|
<bpmndi:BPMNShape id="UserTask_01zzzg9_di" bpmnElement="UserTask_01zzzg9">
|
||||||
<dc:Bounds x="620" y="460" width="100" height="80" />
|
<dc:Bounds x="990" y="460" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="UserTask_0gtuk1e_di" bpmnElement="UserTask_EnterMultiSiteInfo">
|
<bpmndi:BPMNShape id="UserTask_0gtuk1e_di" bpmnElement="UserTask_EnterMultiSiteInfo">
|
||||||
<dc:Bounds x="620" y="210" width="100" height="80" />
|
<dc:Bounds x="990" y="210" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="UserTask_0ebxkp7_di" bpmnElement="UserTask_0ebxkp7">
|
<bpmndi:BPMNShape id="UserTask_0ebxkp7_di" bpmnElement="UserTask_0ebxkp7">
|
||||||
<dc:Bounds x="620" y="90" width="100" height="80" />
|
<dc:Bounds x="990" y="90" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="Activity_0vthni9_di" bpmnElement="Activity_10nxpt2">
|
<bpmndi:BPMNShape id="Activity_0vthni9_di" bpmnElement="Activity_10nxpt2">
|
||||||
<dc:Bounds x="-120" y="285" width="100" height="80" />
|
<dc:Bounds x="250" y="285" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="Activity_0spxv8q_di" bpmnElement="Activity_PBMultiSiteCheckQ12">
|
<bpmndi:BPMNShape id="Activity_0spxv8q_di" bpmnElement="Activity_PBMultiSiteCheckQ12">
|
||||||
<dc:Bounds x="40" y="285" width="100" height="80" />
|
<dc:Bounds x="410" y="285" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="Activity_0ah6heg_di" bpmnElement="Activity_PBMultiSiteCheckQ14">
|
<bpmndi:BPMNShape id="Activity_0ah6heg_di" bpmnElement="Activity_PBMultiSiteCheckQ14">
|
||||||
<dc:Bounds x="200" y="285" width="100" height="80" />
|
<dc:Bounds x="570" y="285" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="Activity_0x7b58m_di" bpmnElement="Activity_PBMultiSiteCheckQ28">
|
<bpmndi:BPMNShape id="Activity_0x7b58m_di" bpmnElement="Activity_PBMultiSiteCheckQ28">
|
||||||
<dc:Bounds x="350" y="285" width="100" height="80" />
|
<dc:Bounds x="720" y="285" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
</bpmndi:BPMNPlane>
|
</bpmndi:BPMNPlane>
|
||||||
</bpmndi:BPMNDiagram>
|
</bpmndi:BPMNDiagram>
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,277 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" id="Definitions_0crc2o7" name="DRD" namespace="http://camunda.org/schema/1.0/dmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
||||||
|
<decision id="Decision_ApprovalInfo" name="Approval Info">
|
||||||
|
<decisionTable id="decisionTable_1">
|
||||||
|
<input id="input_1" label="School">
|
||||||
|
<inputExpression id="inputExpression_1" typeRef="string">
|
||||||
|
<text>PISchool</text>
|
||||||
|
</inputExpression>
|
||||||
|
</input>
|
||||||
|
<output id="OutputClause_1138fqx" label="School" name="ApprvlSchool" typeRef="string" />
|
||||||
|
<output id="output_1" label="Approver 1" name="ApprvlApprvr1" typeRef="string" />
|
||||||
|
<output id="OutputClause_0dcb1cr" label="Approver 1 Role" name="ApprvlApprovrRole1" typeRef="string" />
|
||||||
|
<output id="OutputClause_0mftsw9" label="Approver 2" name="ApprvlApprvr2" typeRef="string" />
|
||||||
|
<output id="OutputClause_0iuw224" label="Approver 2 Role" name="ApprvlApprvrRole2" typeRef="string" />
|
||||||
|
<rule id="DecisionRule_1wge2nn">
|
||||||
|
<inputEntry id="UnaryTests_0mfg1fu">
|
||||||
|
<text>"Architecture"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_074qb28">
|
||||||
|
<text>"Architecture"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0vb9mia">
|
||||||
|
<text>PISupervisor.data.uid</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1pqm0u2">
|
||||||
|
<text>"Supervisor"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_17oc2op">
|
||||||
|
<text>"agc9a"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_08dc6cz">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_1u9orsf">
|
||||||
|
<inputEntry id="UnaryTests_08ormp3">
|
||||||
|
<text>"Arts & Sciences"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0ua8d03">
|
||||||
|
<text>"Arts & Sciences"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1ejs3h2">
|
||||||
|
<text>PISupervisor.data.uid</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0h7eq4l">
|
||||||
|
<text>"Supervisor"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_06pynb2">
|
||||||
|
<text>"dh2t"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0x965p2">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_0k7lbs6">
|
||||||
|
<inputEntry id="UnaryTests_1tws5wb">
|
||||||
|
<text>"Commerce"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0y7bv69">
|
||||||
|
<text>"Commerce"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0sz476a">
|
||||||
|
<text>"dcs8f"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0wbcswk">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0htwmws">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0ntf026">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_0pcsd6s">
|
||||||
|
<inputEntry id="UnaryTests_07uijrb">
|
||||||
|
<text>"Darden"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1he7hp6">
|
||||||
|
<text>"Darden"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0hdskgi">
|
||||||
|
<text>"mw4m"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1uxxdv0">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0q81xjf">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1rp7s1w">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_0qlavkb">
|
||||||
|
<inputEntry id="UnaryTests_1775ht5">
|
||||||
|
<text>"Data Science"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1fxauop">
|
||||||
|
<text>"Data Science"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0w0ksry">
|
||||||
|
<text>"cws3v"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0847zci">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1xjonk2">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1rb6200">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_0vw6027">
|
||||||
|
<inputEntry id="UnaryTests_1hsal1b">
|
||||||
|
<text>"Education"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1uwd4hj">
|
||||||
|
<text>"Education"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1t3c4oj">
|
||||||
|
<text>PISupervisor.data.uid</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0vkosy7">
|
||||||
|
<text>"Supervisor"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1sj3i3e">
|
||||||
|
<text>"cpb8g"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_11ml53c">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_1rgdbw3">
|
||||||
|
<inputEntry id="UnaryTests_022jcbh">
|
||||||
|
<text>"Engineering"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0lzmt29">
|
||||||
|
<text>"Engineering"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0v4df1x">
|
||||||
|
<text>"sb5mc"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_16kmec1">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1bjsn8g">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1rm1jw7">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_1hy1sby">
|
||||||
|
<inputEntry id="UnaryTests_1u52cey">
|
||||||
|
<text>"Law"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1w3vu2k">
|
||||||
|
<text>"Law"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1nrqe2j">
|
||||||
|
<text>"kendrick"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_19n45lv">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0nppbew">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0n1f5l1">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_1nmyhfi">
|
||||||
|
<inputEntry id="UnaryTests_0uqd08s">
|
||||||
|
<text>"Leadership & Public Policy"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0dy02ei">
|
||||||
|
<text>"Leadership & Public Policy"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0rxrqkb">
|
||||||
|
<text>"jps3va"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0ejmi7q">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1ph2wlx">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0i6fih4">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_17uxicj">
|
||||||
|
<inputEntry id="UnaryTests_0kttpy1">
|
||||||
|
<text>"Medicine"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1ops4nm">
|
||||||
|
<text>"Medicine"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1cwa3r0">
|
||||||
|
<text>PISupervisor.data.uid</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0ouvo17">
|
||||||
|
<text>"Supervisor"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_18rte97">
|
||||||
|
<text>"mas3x"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1rxh2p0">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_1t4241f">
|
||||||
|
<inputEntry id="UnaryTests_0xwnrta">
|
||||||
|
<text>"Nursing"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1va63rh">
|
||||||
|
<text>"Nursing"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_125fuie">
|
||||||
|
<text>"jla7e"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0h1e3bv">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0e8b9ui">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1mq49yg">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_0jnp1wp">
|
||||||
|
<inputEntry id="UnaryTests_164zt99">
|
||||||
|
<text>"Continuing Education"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1h81xwr">
|
||||||
|
<text>"Continuing Education"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_12sudp0">
|
||||||
|
<text>"ado4v"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1jkrv7z">
|
||||||
|
<text>"Associate Research Dean"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0qelytu">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_19tc8kf">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
<rule id="DecisionRule_1ynkili">
|
||||||
|
<inputEntry id="UnaryTests_1dupb6e">
|
||||||
|
<text>"Provost Office"</text>
|
||||||
|
</inputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1w13wuw">
|
||||||
|
<text>"Provost Office"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_13xizrn">
|
||||||
|
<text>"rammk"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_166hajt">
|
||||||
|
<text>"VP of Research"</text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_1xazzzn">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
<outputEntry id="LiteralExpression_0zr3ar6">
|
||||||
|
<text></text>
|
||||||
|
</outputEntry>
|
||||||
|
</rule>
|
||||||
|
</decisionTable>
|
||||||
|
</decision>
|
||||||
|
</definitions>
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,860 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1oogn9j" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
||||||
|
<bpmn:process id="Process_0ssahs9" isExecutable="true">
|
||||||
|
<bpmn:startEvent id="StartEvent_1">
|
||||||
|
<bpmn:outgoing>SequenceFlow_05ja25w</bpmn:outgoing>
|
||||||
|
</bpmn:startEvent>
|
||||||
|
<bpmn:manualTask id="ManualTask_Instructions" name="Read RRP Instructions">
|
||||||
|
<bpmn:documentation>## Beta Stage: All data entered will be destroyed before public launch
|
||||||
|
|
||||||
|
|
||||||
|
### UNIVERSITY OF VIRGINIA RESEARCH
|
||||||
|
[From Research Ramp-up Guidance](https://research.virginia.edu/research-ramp-guidance)
|
||||||
|
|
||||||
|
|
||||||
|
#### Support
|
||||||
|
Report problems and/or submit questions to: askresearch@virginia.edu
|
||||||
|
|
||||||
|
#### Research Guidance
|
||||||
|
|
||||||
|
Our general principle is that only research activities requiring on-Grounds presence would be conducted on-Grounds. All other research-related work would continue to be performed by telework until restrictions are lifted. Separate school, department and building specific plans should supplement these guidelines.
|
||||||
|
|
||||||
|
|
||||||
|
For research that needs to be on Grounds, the plan is to ramp up in phases with emphasis on safety. The goal of this document is to provide a central framework for resuming activities while allowing for coordinated school specific implementation strategies.
|
||||||
|
|
||||||
|
|
||||||
|
The success of the ramp up depends on each researcher placing the safety of themselves and the people around them first, while conducting their research. In order to reduce our risks as much as possible, this must be a partnership between the researchers and the administration.
|
||||||
|
|
||||||
|
|
||||||
|
Schools are developing a process for the approval of ramp up requests and enforcement of safety guidelines described in this document. The VPR office is working with the schools to provide the necessary support for business process infrastructure, and working with the COO’s office to coordinate the acquisition of supplies necessary including face coverings and sanitizing supplies.
|
||||||
|
|
||||||
|
**Instructions for Submitting:**
|
||||||
|
|
||||||
|
|
||||||
|
1. The Research Ramp-up Plan allows for one request to be entered for a single Principle Investigator. In the form that follows enter the Primary Investigator this request is for and other identifying information. The PI's School and Supervisor will be used as needed for approval routing.
|
||||||
|
2. Provide all available information in the forms that follow to provide an overview of where the research will resume, who will be involved, what supporting resources will be needed and what steps you have taken to assure compliance with [Research Ramp-up Guidance](https://research.virginia.edu/research-ramp-guidance).
|
||||||
|
3. After all forms have been completed, you will be presented with the option to create your Research Recovery Plan in Word format. Download the document and review it. If you see any corrections that need to be made, return to the corresponding form and make the correction.
|
||||||
|
4. Once the generated Research Recovery Plan is finalize, proceed to the Plan Submission step to submit your plan for approval.</bpmn:documentation>
|
||||||
|
<bpmn:incoming>SequenceFlow_05ja25w</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>SequenceFlow_0h50bp3</bpmn:outgoing>
|
||||||
|
</bpmn:manualTask>
|
||||||
|
<bpmn:userTask id="Activity-PI_Info" name="Enter PI Info" camunda:formKey="PI Information">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="PIComputingID" label="Primary Investigator" type="autocomplete">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="placeholder" value="wxy0z or Smith" />
|
||||||
|
<camunda:property id="ldap.lookup" value="true" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
<camunda:constraint name="description" config="Find the PI by entering Computing ID or Last Name." />
|
||||||
|
<camunda:constraint name="ldap.lookup" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PISchool" label="PI's School" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="spreadsheet.name" value="SchoolList.xls" />
|
||||||
|
<camunda:property id="spreadsheet.value.column" value="Value" />
|
||||||
|
<camunda:property id="spreadsheet.label.column" value="School Name" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PIPrimaryDeptArchitecture" label="PI's Primary Architecture Department" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="spreadsheet.name" value="DepartmentList-Architecture.xlsx" />
|
||||||
|
<camunda:property id="spreadsheet.value.column" value="Value" />
|
||||||
|
<camunda:property id="spreadsheet.label.column" value="Label" />
|
||||||
|
<camunda:property id="hide_expression" value="(model.PISchool && model.PISchool !== "Architecture") || model.PISchool === null" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PIPrimaryDeptArtsSciences" label="PI's Primary Arts & Sciences Department" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="spreadsheet.name" value="DepartmentList-ArtsSciences.xlsx" />
|
||||||
|
<camunda:property id="spreadsheet.value.column" value="Value" />
|
||||||
|
<camunda:property id="spreadsheet.label.column" value="Label" />
|
||||||
|
<camunda:property id="hide_expression" value="(model.PISchool && model.PISchool !== "Arts & Sciences") || model.PISchool === null" />
|
||||||
|
<camunda:property id="description" value="Type key words to find department" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PIPrimaryDeptEducation" label="PI's Primary Education Department" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="spreadsheet.name" value="DepartmentList-Education.xlsx" />
|
||||||
|
<camunda:property id="spreadsheet.value.column" value="Value" />
|
||||||
|
<camunda:property id="spreadsheet.label.column" value="Label" />
|
||||||
|
<camunda:property id="hide_expression" value="(model.PISchool && model.PISchool !== "Education") || model.PISchool === null" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PIPrimaryDeptEngineering" label="PI's Primary Engineering Department" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="spreadsheet.name" value="DepartmentList-Engineering.xlsx" />
|
||||||
|
<camunda:property id="spreadsheet.value.column" value="Value" />
|
||||||
|
<camunda:property id="spreadsheet.label.column" value="Label" />
|
||||||
|
<camunda:property id="hide_expression" value="(model.PISchool && model.PISchool !== "Engineering") || model.PISchool === null" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PIPrimaryDeptMedicine" label="PI's Primary Medicine Department/Center" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="spreadsheet.name" value="DepartmentList-Medicine.xlsx" />
|
||||||
|
<camunda:property id="spreadsheet.value.column" value="Value" />
|
||||||
|
<camunda:property id="spreadsheet.label.column" value="Label" />
|
||||||
|
<camunda:property id="hide_expression" value="(model.PISchool && model.PISchool !== "Medicine") || model.PISchool === null" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PIPrimaryDeptProvostOffice" label="PI's Primary Provost Office Department/Center" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="spreadsheet.name" value="DepartmentList-ProvostOffice.xlsx" />
|
||||||
|
<camunda:property id="spreadsheet.value.column" value="Value" />
|
||||||
|
<camunda:property id="spreadsheet.label.column" value="Label" />
|
||||||
|
<camunda:property id="hide_expression" value="(model.PISchool && model.PISchool !== "Provost Office") || model.PISchool === null" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PIPrimaryDeptOther" label="Primary Department " type="string">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="hide_expression" value="(model.PIPrimaryDeptArchitecture === null && model.PIPrimaryDeptArtsSciences === null && model.PIPrimaryDeptEducation === null && model.PIPrimaryDeptEngineering === null && model.PIPrimaryDeptMedicine === null && model.PIPrimaryDeptProvostOffice === null) || (model.PIPrimaryDeptArchitecture !== "Other" && model.PIPrimaryDeptArtsSciences !== "Other" && model.PIPrimaryDeptEducation !== "Other" && model.PIPrimaryDeptEngineering !== "Other" && model.PIPrimaryDeptMedicine !== "Other" && model.PIPrimaryDeptProvostOffice !== "Other")" />
|
||||||
|
<camunda:property id="description" value="Enter the PI's Primary Department " />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PISupervisor" label="Pi's Supervisor" type="autocomplete">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Find the PI's Supervisor by entering Computing I D or Last Name." />
|
||||||
|
<camunda:property id="ldap.lookup" value="true" />
|
||||||
|
<camunda:property id="placeholder" value="wxy0z or Smith" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="Required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>SequenceFlow_0h50bp3</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_16y8glw</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:sequenceFlow id="SequenceFlow_0h50bp3" sourceRef="ManualTask_Instructions" targetRef="Activity-PI_Info" />
|
||||||
|
<bpmn:sequenceFlow id="SequenceFlow_05ja25w" sourceRef="StartEvent_1" targetRef="ManualTask_Instructions" />
|
||||||
|
<bpmn:userTask id="Personnel" name="Enter Personnel" camunda:formKey="Personnel">
|
||||||
|
<bpmn:documentation>#### People for whom you are requesting access
|
||||||
|
Information on all researchers you are requesting approval for reentry into the lab/research space for conducting research on-Grounds. (If there are personnel already working in the space, include them).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Note: no undergraduates will be allowed to work on-Grounds during Phase II.**</bpmn:documentation>
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="PersonnelComputingID" label="Computer ID" type="autocomplete">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="repeat" value="Personnel" />
|
||||||
|
<camunda:property id="ldap.lookup" value="true" />
|
||||||
|
<camunda:property id="description" value="Find by entering Computing ID or Last Name." />
|
||||||
|
<camunda:property id="placeholder" value="wxy0z or Smith" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PersonnelType" label="Personnel Type" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="repeat" value="Personnel" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:value id="Faculty" name="Faculty" />
|
||||||
|
<camunda:value id="AcademicResearcher" name="Academic Researcher" />
|
||||||
|
<camunda:value id="Staff" name="Staff" />
|
||||||
|
<camunda:value id="Postdoc" name="Postdoc" />
|
||||||
|
<camunda:value id="DoctoralStudent" name="Doctoral Student" />
|
||||||
|
<camunda:value id="UndeGraduate" name="Undergraduate" />
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PersonnelSpace" label="Space they will work in" type="textarea">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="rows" value="2" />
|
||||||
|
<camunda:property id="autosize" value="true" />
|
||||||
|
<camunda:property id="description" value="Provide building and room number for each lab space this person will work in" />
|
||||||
|
<camunda:property id="repeat" value="Personnel" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PersonnelJustification" label="Research Justification" type="textarea">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Provide a brief description of this person’s research and justification why this is critical research." />
|
||||||
|
<camunda:property id="rows" value="3" />
|
||||||
|
<camunda:property id="autosize" value="true" />
|
||||||
|
<camunda:property id="repeat" value="Personnel" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PersonnelWeeklySchedule" label="Personnel Weekly Schedule" type="files">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Upload a file or files of proposed weekly schedules for all personnel which are representative of compliance with ramp-up guidance." />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_1eiud85</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_1nbjr72</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:userTask id="UserTask_CoreResource" name="Enter Core Resources" camunda:formKey="Core Resources">
|
||||||
|
<bpmn:documentation>#### If applicable, provide a list of any [Core Resources](https://research.virginia.edu/research-core-resources) you will utilize space or instruments in and name/email of contact person in the core you have coordinated your plan with. (Core facility managers are responsible for developing a plan for their space)</bpmn:documentation>
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="isCoreResourcesUse" label="Core Resources Use" type="boolean">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Will you need access to core resources to resume research?" />
|
||||||
|
<camunda:property id="help" value="[From Research Ramp-up Guidance](https://research.virginia.edu/research-ramp-guidance)\n#### ADDITIONAL CONSIDERATIONS FOR A SUCCESSFUL RAMP-UP:\n##### Lab-based Research\n- (1) Core facilities operational\n\n[EHS Lab Ramp up Checklist for Laboratories](https://research.virginia.edu/sites/vpr/files/2020-05/EHS.LabRampUpChecklistForLaboratories_0_0.pdf)\n##### General\n- Note that shared facilities, such as stockrooms or core labs, may be on different ramp up schedules or in more demand than during normal operation." />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="CoreResources" label="Core Resources" type="textarea">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="rows" value="10" />
|
||||||
|
<camunda:property id="autosize" value="true" />
|
||||||
|
<camunda:property id="hide_expression" value="!model.isCoreResourcesUse | model.isCoreResourcesUse == null" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_15zy1q7</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_12ie6w0</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:endEvent id="EndEvent_09wp7av">
|
||||||
|
<bpmn:documentation>#### End of Workflow
|
||||||
|
Place instruction here,</bpmn:documentation>
|
||||||
|
<bpmn:incoming>Flow_05w8yd6</bpmn:incoming>
|
||||||
|
</bpmn:endEvent>
|
||||||
|
<bpmn:sequenceFlow id="Flow_1e2qi9s" sourceRef="Activity_AcknowledgePlanReview" targetRef="Activity_ApprovalInfo" />
|
||||||
|
<bpmn:manualTask id="Activity_AcknowledgePlanReview" name="Acknowledge Plan Review">
|
||||||
|
<bpmn:documentation>Your Research Ramp-up Plan has been generated and is available in the Files pop-out found in the upper left hand corner of this application Click on the file name link to download the MS Word file to download, open and review. If changes are needed, choose the appropriate menu choice to make your edits, clicking Save when done. Note that you will need to revisit subsequent steps so the application can check to see if your edits impacted future workflow decisions. All your data will be persevered and you will need to click the Save button on each step to proceed.
|
||||||
|
|
||||||
|
When your Research Ramp-up Plan is complete and ready to submit for review and approval, click the Continue button below.</bpmn:documentation>
|
||||||
|
<bpmn:incoming>Flow_0aqgwvu</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_1e2qi9s</bpmn:outgoing>
|
||||||
|
</bpmn:manualTask>
|
||||||
|
<bpmn:userTask id="Activity_SharedSpaceInfo" name="Enter Shared Space" camunda:formKey="Space Involved in this Request">
|
||||||
|
<bpmn:documentation>#### Space used by {{ PIComputingID.label }} and shared with other PIs. If all space is exclusive and not shared with one or more other investigators, Click Save to skip this section and proceed to the next section.</bpmn:documentation>
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="SharedSpaceBuilding" label="Building Name" type="autocomplete">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Select the building where this shared lab space is housed." />
|
||||||
|
<camunda:property id="spreadsheet.name" value="BuildingList.xls" />
|
||||||
|
<camunda:property id="spreadsheet.value.column" value="Value" />
|
||||||
|
<camunda:property id="spreadsheet.label.column" value="Building Name" />
|
||||||
|
<camunda:property id="repeat" value="Shared" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="SharedSpaceRoomID" label="Shared Space Room ID" type="string">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="repeat" value="Shared" />
|
||||||
|
<camunda:property id="description" value="Enter the shared room number or other unique identifier" />
|
||||||
|
<camunda:property id="hide_expression" value="model.SharedSpaceBuilding === "Other"" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="ShareSpaceRoomIDBuilding" label="Room No, and Building Name" type="string">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Enter the Room No and Building of your shared space." />
|
||||||
|
<camunda:property id="hide_expression" value="model.SharedSpaceBuilding !== "Other" | model.SharedSpaceBuilding === null" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="SharedSpaceAMComputingID" label="Area Monitor" type="autocomplete">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="ldap.lookup" value="true" />
|
||||||
|
<camunda:property id="placeholder" value="wxy0z or Smith" />
|
||||||
|
<camunda:property id="description" value="Enter Area Monitor's Computing ID or last name and select Area Monitor. Leave blank if not known." />
|
||||||
|
<camunda:property id="repeat" value="Shared" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="SharedSpaceSqFt" label="Enter the number of square feet usable by personnel in this space." type="long">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="repeat" value="Shared" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="SharedSpacePercentUsable" label="Percent of Space Usable By Personnel" type="long">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="repeat" value="Shared" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="min" config="1" />
|
||||||
|
<camunda:constraint name="max" config="100" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="SharedSpaceMaxPersonnel" label="Maximum Number of Personnel Occupying Space" type="long">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Enter the maximum number of personnel, including yourself, who will occupy this space." />
|
||||||
|
<camunda:property id="repeat" value="Shared" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="SharedSpacePI" label="Shared Space PI(s)" type="textarea">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="For each PI you share this space with, enter their Name, School, Department and Email Address." />
|
||||||
|
<camunda:property id="rows" value="5" />
|
||||||
|
<camunda:property id="repeat" value="Shared" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_19xeq76</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_16342pm</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:parallelGateway id="Gateway_0frfdnc">
|
||||||
|
<bpmn:incoming>Flow_1v7r1tg</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_19xeq76</bpmn:outgoing>
|
||||||
|
<bpmn:outgoing>Flow_0qf2y84</bpmn:outgoing>
|
||||||
|
<bpmn:outgoing>Flow_15zy1q7</bpmn:outgoing>
|
||||||
|
<bpmn:outgoing>Flow_0ya8hw8</bpmn:outgoing>
|
||||||
|
</bpmn:parallelGateway>
|
||||||
|
<bpmn:parallelGateway id="Gateway_1vj4zd3">
|
||||||
|
<bpmn:incoming>Flow_0tk64b6</bpmn:incoming>
|
||||||
|
<bpmn:incoming>Flow_12ie6w0</bpmn:incoming>
|
||||||
|
<bpmn:incoming>Flow_0zz2hbq</bpmn:incoming>
|
||||||
|
<bpmn:incoming>Flow_16342pm</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_1eiud85</bpmn:outgoing>
|
||||||
|
</bpmn:parallelGateway>
|
||||||
|
<bpmn:sequenceFlow id="Flow_19xeq76" sourceRef="Gateway_0frfdnc" targetRef="Activity_SharedSpaceInfo" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_16342pm" sourceRef="Activity_SharedSpaceInfo" targetRef="Gateway_1vj4zd3" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_16y8glw" sourceRef="Activity-PI_Info" targetRef="Activity_1u58hox" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_0qf2y84" sourceRef="Gateway_0frfdnc" targetRef="Activity_ExclusiveSpace" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_0tk64b6" sourceRef="Activity_ExclusiveSpace" targetRef="Gateway_1vj4zd3" />
|
||||||
|
<bpmn:userTask id="Activity_ExclusiveSpace" name="Enter Exclusive Space" camunda:formKey="ExclusiveSpace">
|
||||||
|
<bpmn:documentation>#### Space managed exclusively by {{ PIComputingID.label }}
|
||||||
|
Submit one entry for each space the PI is the exclusive investigator. If all space is shared with one or more other investigators, Click Save to skip this section and proceed to the Shared Space section.</bpmn:documentation>
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="ExclusiveSpaceBuilding" label="Room No. & Building Name" type="autocomplete">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Type key word to find the building in which the lab is located." />
|
||||||
|
<camunda:property id="spreadsheet.name" value="BuildingList.xls" />
|
||||||
|
<camunda:property id="spreadsheet.value.column" value="Value" />
|
||||||
|
<camunda:property id="spreadsheet.label.column" value="Building Name" />
|
||||||
|
<camunda:property id="repeat" value="Exclusive" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="ExclusiveSpaceRoomID" label="Exclusive Space Room ID" type="string">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="hide_expression" value="model.ExclusiveSpaceBuilding === "Other"" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="ExclusiveSpaceRoomIDBuilding" label="Room No, and Building Name" type="string">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Enter the Room No and Building of your exclusive space." />
|
||||||
|
<camunda:property id="hide_expression" value="model.ExclusiveSpaceBuilding !== "Other" | model.ExclusiveSpaceBuilding === null" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="ExclusiveSpaceType" label="Space Room Type" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="repeat" value="Exclusive" />
|
||||||
|
<camunda:property id="enum_type" value="radio" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
<camunda:value id="Lab" name="Lab" />
|
||||||
|
<camunda:value id="Office" name="Office" />
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="ExclusiveSpaceAMComputingID" label="Area Monitor" type="autocomplete">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="ldap.lookup" value="true" />
|
||||||
|
<camunda:property id="description" value="Enter Area Monitor's Computing ID or last name and select Area Monitor. Leave blank if not known." />
|
||||||
|
<camunda:property id="repeat" value="Exclusive" />
|
||||||
|
<camunda:property id="placeholder" value="wxy0z or Smith" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="ExclusiveSpaceSqFt" label="Enter the number of square feet usable by personnel in this space." type="long">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="repeat" value="Exclusive" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="ExclusiveSpacePercentUsable" label="Percent of Space Usable By Personnel" type="long">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="repeat" value="Exclusive" />
|
||||||
|
<camunda:property id="description" value="Enter number between 1 & 100 which indicates the percent of space this space personnel will work and move around in during the workday." />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="min" config="1" />
|
||||||
|
<camunda:constraint name="max" config="100" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="ExclusiveSpaceMaxPersonnel" label="Maximum Number of Personnel Occupying Space" type="long">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Enter the maximum number of personnel, including yourself, who will occupy this space." />
|
||||||
|
<camunda:property id="repeat" value="Exclusive" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_0qf2y84</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_0tk64b6</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_15zy1q7" sourceRef="Gateway_0frfdnc" targetRef="UserTask_CoreResource" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_12ie6w0" sourceRef="UserTask_CoreResource" targetRef="Gateway_1vj4zd3" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_0ya8hw8" sourceRef="Gateway_0frfdnc" targetRef="Activity_nonUVASpaces" />
|
||||||
|
<bpmn:userTask id="Activity_nonUVASpaces" name="Enter non-UVA Spaces" camunda:formKey="nonUVA Spaces">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="isNonUVASpaces" label="non-UVA Spaces?" type="boolean">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Does any of your research occur in non-UVA spaces? (For example, field stations or UVA leased buildings off Grounds) " />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="NonUVASpaces" label="Where?" type="textarea">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="description" value="Does any of your research occur in non-UVA spaces? (For example, field stations or UVA leased buildings off Grounds) " />
|
||||||
|
<camunda:property id="hide_expression" value="!model.isNonUVASpaces | model.isNonUVASpaces == null" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_0ya8hw8</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_0zz2hbq</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_0zz2hbq" sourceRef="Activity_nonUVASpaces" targetRef="Gateway_1vj4zd3" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_1eiud85" sourceRef="Gateway_1vj4zd3" targetRef="Personnel" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_1nbjr72" sourceRef="Personnel" targetRef="Gateway_18jn18b" />
|
||||||
|
<bpmn:userTask id="Activity_DistanceReq" name="Enter Distancing Requirements" camunda:formKey="Distancing Requirements">
|
||||||
|
<bpmn:documentation>#### Distancing requirements:
|
||||||
|
Maintain social distancing by designing space between people to be at least 9 feet during prolonged work which will be accomplished by restricting the number of people in the lab to a density of ~250 sq. ft. /person in lab areas. When moving around, a minimum of 6 feet social distancing is required. Ideally only one person per lab bench and not more than one person can work at the same time in the same bay.</bpmn:documentation>
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="CIDR_TotalSqFt" label="What is the total square footage of your lab?" type="long">
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="CIDR_MaxPersonnel" label="How many personnel will be using the lab at any one time, at a maximum?" type="long">
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_0p2r1bo</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_0tz5c2v</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_0p2r1bo" sourceRef="Gateway_18jn18b" targetRef="Activity_DistanceReq" />
|
||||||
|
<bpmn:parallelGateway id="Gateway_18jn18b">
|
||||||
|
<bpmn:incoming>Flow_1nbjr72</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_0p2r1bo</bpmn:outgoing>
|
||||||
|
<bpmn:outgoing>Flow_0mkh1wn</bpmn:outgoing>
|
||||||
|
<bpmn:outgoing>Flow_1yqkpgu</bpmn:outgoing>
|
||||||
|
<bpmn:outgoing>Flow_1c6m5wv</bpmn:outgoing>
|
||||||
|
</bpmn:parallelGateway>
|
||||||
|
<bpmn:sequenceFlow id="Flow_0mkh1wn" sourceRef="Gateway_18jn18b" targetRef="Activity_PWA" />
|
||||||
|
<bpmn:userTask id="Activity_PWA" name="Enter Physical Work Arrangements" camunda:formKey="Physical Work Arrangements">
|
||||||
|
<bpmn:documentation>Describe physical work arrangements for each lab. Show schematic of the lab and space organization to meet the distancing guidelines (see key safety expectations for ramp-up).
|
||||||
|
- Show gross dimensions, location of desks, and equipment in blocks (not details) that show available space for work and foot traffic.
|
||||||
|
- Indicate total square footage for every lab/space that you are requesting adding personnel to in this application. If you would like help obtaining a floor plan for your lab, your department or deans office can help. You can also create a hand drawing/block diagram of your space and the location of objects on a graph paper.
|
||||||
|
- Upload your physical layout and workspace organization in the form of a jpg image or a pdf file. This can be hand-drawn or actual floor plans.
|
||||||
|
- Show and/or describe designated work location for each member (during their shift) in the lab when multiple members are present at a time to meet the distancing guidelines.
|
||||||
|
- Provide a foot traffic plan (on the schematic) to indicate how people can move around while maintaining distancing requirements. This can be a freeform sketch on your floor plan showing where foot traffic can occur in your lab, and conditions, if any, to ensure distancing at all times. (e.g., direction to walk around a lab bench, rules for using shared equipment located in the lab, certain areas of lab prohibited from access, etc.).
|
||||||
|
- Provide your initial weekly laboratory schedule (see excel template) for all members that you are requesting access for, indicating all shifts as necessary. If schedule changes, please submit your revised schedule through the web portal.</bpmn:documentation>
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="PWADescribe" label="Describe" type="textarea">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="rows" value="10" />
|
||||||
|
<camunda:property id="autosize" value="true" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="PWAFiles" label="Upload supporting files" type="files" />
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_0mkh1wn</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_0zrsh65</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_1yqkpgu" sourceRef="Gateway_18jn18b" targetRef="Activity_HSR" />
|
||||||
|
<bpmn:userTask id="Activity_HSR" name="Enter Health Safety Requirements" camunda:formKey="Lab Plan">
|
||||||
|
<bpmn:documentation>#### Health Safety Requirements:
|
||||||
|
Use EHS [Lab Safety Plan During COVID 19 template](https://www.google.com/url?q=http://ehs.virginia.edu/files/Lab-Safety-Plan-During-COVID-19.docx&source=gmail&ust=1590687968958000&usg=AFQjCNE83uGDFtxGkKaxjuXGhTocu-FDmw) to create and upload a copy of your laboratory policy statement to all members which includes at a minimum the following details:
|
||||||
|
- Laboratory face covering rules, use of other PPE use as required
|
||||||
|
- Adherence to individual schedules, check-in, check out requirements
|
||||||
|
- Completion of online EHS safety training requirement
|
||||||
|
- Health self-attestation requirement
|
||||||
|
- Sanitizing procedures including frequency and type of disinfectants to use
|
||||||
|
-
|
||||||
|
- Where and how to obtain PPE including face covering</bpmn:documentation>
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="LabPlan" label="Upload Lab Plan" type="file" />
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_1yqkpgu</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_1ox5nv6</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_1c6m5wv" sourceRef="Gateway_18jn18b" targetRef="Activity_OtherReq" />
|
||||||
|
<bpmn:userTask id="Activity_OtherReq" name="Enter Other Requirements" camunda:formKey="Other Requirements">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="isAnimalResearch" label="Will you be using animals in your research? " type="boolean">
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="isAnimalResearchCoordinated" label="Have you coordinated with the vivarium manager to meet your needs?" type="boolean">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="hide_expression" value="!model.isAnimalResearch | model.isAnimalResearch == null" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="isHumanSubjects" label="Does your research involve human subjects? " type="boolean">
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="IRBApprovalRelevantNumbers" label="List IRB Approval Relevant Numbers" type="textarea">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="rows" value="5" />
|
||||||
|
<camunda:property id="hide_expression" value="!model.isHumanSubjects | model.isHumanSubjects == null" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="isNecessarySupplies" label="Do you have the necessary materials, supplies, cleaning supplies and PPE for your laboratory?" type="boolean">
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="SupplyList" label="List your needs" type="textarea">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="hide_expression" value="model.isNecessarySupplies | model.isNecessarySupplies == null" />
|
||||||
|
<camunda:property id="rows" value="5" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:validation>
|
||||||
|
<camunda:constraint name="required" config="true" />
|
||||||
|
</camunda:validation>
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_1c6m5wv</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_0qbi47d</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:manualTask id="Activity_SubmitPlan" name="Acknowledge Plan Submission">
|
||||||
|
<bpmn:documentation>#### By submitting this request, you understand that every member listed in this form for on Grounds laboratory access will:
|
||||||
|
- Complete online COVID awareness & precaution training module (link forthcoming-May 25)
|
||||||
|
- Complete daily health acknowledgement form signed (electronically) –email generated daily to those listed on your plan for access to on Grounds lab/research space
|
||||||
|
- Fill out daily work attendance log for all lab members following your school process to check-in and out of work each day.</bpmn:documentation>
|
||||||
|
<bpmn:incoming>Flow_08njvvi</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_0j4rs82</bpmn:outgoing>
|
||||||
|
</bpmn:manualTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_0zrsh65" sourceRef="Activity_PWA" targetRef="Gateway_0sijkgx" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_0tz5c2v" sourceRef="Activity_DistanceReq" targetRef="Gateway_0sijkgx" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_1ox5nv6" sourceRef="Activity_HSR" targetRef="Gateway_0sijkgx" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_0qbi47d" sourceRef="Activity_OtherReq" targetRef="Gateway_0sijkgx" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_06873ag" sourceRef="Gateway_0sijkgx" targetRef="Activity_1tub2mc" />
|
||||||
|
<bpmn:parallelGateway id="Gateway_0sijkgx">
|
||||||
|
<bpmn:incoming>Flow_0zrsh65</bpmn:incoming>
|
||||||
|
<bpmn:incoming>Flow_0tz5c2v</bpmn:incoming>
|
||||||
|
<bpmn:incoming>Flow_1ox5nv6</bpmn:incoming>
|
||||||
|
<bpmn:incoming>Flow_0qbi47d</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_06873ag</bpmn:outgoing>
|
||||||
|
</bpmn:parallelGateway>
|
||||||
|
<bpmn:scriptTask id="Activity_1tub2mc" name="Generate RRP">
|
||||||
|
<bpmn:incoming>Flow_06873ag</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_0aqgwvu</bpmn:outgoing>
|
||||||
|
<bpmn:script>CompleteTemplate ResearchRampUpPlan.docx RESEARCH_RAMPUP</bpmn:script>
|
||||||
|
</bpmn:scriptTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_0aqgwvu" sourceRef="Activity_1tub2mc" targetRef="Activity_AcknowledgePlanReview" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_0j4rs82" sourceRef="Activity_SubmitPlan" targetRef="Activity_0absozl" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_07ge8uf" sourceRef="Activity_0absozl" targetRef="Activity_ReviewStatus" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_1ufh44h" sourceRef="Activity_ReviewStatus" targetRef="Activity_0u9ic8o" />
|
||||||
|
<bpmn:userTask id="Activity_ReviewStatus" name="Update Review Status" camunda:formKey="Review Status">
|
||||||
|
<bpmn:extensionElements>
|
||||||
|
<camunda:formData>
|
||||||
|
<camunda:formField id="ApprovalReceived" label="Please Confirm:" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="enum_type" value="checkbox" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:value id="ApprovalNotificationReceived" name="Approval Notification Received" />
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="RequiredTraining" label="Please Confirm:" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="enum_type" value="checkbox" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:value id="AllRequiredTraining" name="All Required Training Completed" />
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="NeededSupplies" label="Please Confirm"" type="enum">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="enum_type" value="checkbox" />
|
||||||
|
</camunda:properties>
|
||||||
|
<camunda:value id="NeededSupplies" name="All Supplies Needed Have Been Secured" />
|
||||||
|
</camunda:formField>
|
||||||
|
</camunda:formData>
|
||||||
|
</bpmn:extensionElements>
|
||||||
|
<bpmn:incoming>Flow_07ge8uf</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_1ufh44h</bpmn:outgoing>
|
||||||
|
</bpmn:userTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_05w8yd6" sourceRef="Activity_1pklpot" targetRef="EndEvent_09wp7av" />
|
||||||
|
<bpmn:sequenceFlow id="Flow_08njvvi" sourceRef="Activity_ApprovalInfo" targetRef="Activity_SubmitPlan" />
|
||||||
|
<bpmn:businessRuleTask id="Activity_ApprovalInfo" name="Approval Info" camunda:decisionRef="Decision_ApprovalInfo">
|
||||||
|
<bpmn:incoming>Flow_1e2qi9s</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_08njvvi</bpmn:outgoing>
|
||||||
|
</bpmn:businessRuleTask>
|
||||||
|
<bpmn:manualTask id="Activity_1pklpot" name="Review What's Next?">
|
||||||
|
<bpmn:incoming>Flow_0cpmvcw</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_05w8yd6</bpmn:outgoing>
|
||||||
|
</bpmn:manualTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_0cpmvcw" sourceRef="Activity_0u9ic8o" targetRef="Activity_1pklpot" />
|
||||||
|
<bpmn:manualTask id="Activity_0u9ic8o" name="Send Area Monitor Notification">
|
||||||
|
<bpmn:incoming>Flow_1ufh44h</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_0cpmvcw</bpmn:outgoing>
|
||||||
|
</bpmn:manualTask>
|
||||||
|
<bpmn:scriptTask id="Activity_0absozl" name="Execute Plan Submission">
|
||||||
|
<bpmn:incoming>Flow_0j4rs82</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_07ge8uf</bpmn:outgoing>
|
||||||
|
<bpmn:script>RequestApproval ApprvlApprvr1 ApprvlApprvr2</bpmn:script>
|
||||||
|
</bpmn:scriptTask>
|
||||||
|
<bpmn:sequenceFlow id="Flow_1v7r1tg" sourceRef="Activity_1u58hox" targetRef="Gateway_0frfdnc" />
|
||||||
|
<bpmn:scriptTask id="Activity_1u58hox" name="Update Request">
|
||||||
|
<bpmn:incoming>Flow_16y8glw</bpmn:incoming>
|
||||||
|
<bpmn:outgoing>Flow_1v7r1tg</bpmn:outgoing>
|
||||||
|
<bpmn:script>UpdateStudy title:PIComputingID.label pi:PIComputingID.value</bpmn:script>
|
||||||
|
</bpmn:scriptTask>
|
||||||
|
</bpmn:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0ssahs9">
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1v7r1tg_di" bpmnElement="Flow_1v7r1tg">
|
||||||
|
<di:waypoint x="630" y="307" />
|
||||||
|
<di:waypoint x="685" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0cpmvcw_di" bpmnElement="Flow_0cpmvcw">
|
||||||
|
<di:waypoint x="2470" y="307" />
|
||||||
|
<di:waypoint x="2520" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_08njvvi_di" bpmnElement="Flow_08njvvi">
|
||||||
|
<di:waypoint x="1900" y="307" />
|
||||||
|
<di:waypoint x="1930" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_05w8yd6_di" bpmnElement="Flow_05w8yd6">
|
||||||
|
<di:waypoint x="2620" y="307" />
|
||||||
|
<di:waypoint x="2692" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1ufh44h_di" bpmnElement="Flow_1ufh44h">
|
||||||
|
<di:waypoint x="2330" y="307" />
|
||||||
|
<di:waypoint x="2370" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_07ge8uf_di" bpmnElement="Flow_07ge8uf">
|
||||||
|
<di:waypoint x="2180" y="307" />
|
||||||
|
<di:waypoint x="2230" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0j4rs82_di" bpmnElement="Flow_0j4rs82">
|
||||||
|
<di:waypoint x="2030" y="307" />
|
||||||
|
<di:waypoint x="2080" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0aqgwvu_di" bpmnElement="Flow_0aqgwvu">
|
||||||
|
<di:waypoint x="1640" y="307" />
|
||||||
|
<di:waypoint x="1670" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_06873ag_di" bpmnElement="Flow_06873ag">
|
||||||
|
<di:waypoint x="1495" y="307" />
|
||||||
|
<di:waypoint x="1540" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0qbi47d_di" bpmnElement="Flow_0qbi47d">
|
||||||
|
<di:waypoint x="1400" y="510" />
|
||||||
|
<di:waypoint x="1470" y="510" />
|
||||||
|
<di:waypoint x="1470" y="332" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1ox5nv6_di" bpmnElement="Flow_1ox5nv6">
|
||||||
|
<di:waypoint x="1400" y="380" />
|
||||||
|
<di:waypoint x="1470" y="380" />
|
||||||
|
<di:waypoint x="1470" y="332" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0tz5c2v_di" bpmnElement="Flow_0tz5c2v">
|
||||||
|
<di:waypoint x="1400" y="120" />
|
||||||
|
<di:waypoint x="1470" y="120" />
|
||||||
|
<di:waypoint x="1470" y="282" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0zrsh65_di" bpmnElement="Flow_0zrsh65">
|
||||||
|
<di:waypoint x="1400" y="240" />
|
||||||
|
<di:waypoint x="1470" y="240" />
|
||||||
|
<di:waypoint x="1470" y="282" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1c6m5wv_di" bpmnElement="Flow_1c6m5wv">
|
||||||
|
<di:waypoint x="1230" y="332" />
|
||||||
|
<di:waypoint x="1230" y="510" />
|
||||||
|
<di:waypoint x="1300" y="510" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1yqkpgu_di" bpmnElement="Flow_1yqkpgu">
|
||||||
|
<di:waypoint x="1230" y="332" />
|
||||||
|
<di:waypoint x="1230" y="380" />
|
||||||
|
<di:waypoint x="1300" y="380" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0mkh1wn_di" bpmnElement="Flow_0mkh1wn">
|
||||||
|
<di:waypoint x="1230" y="282" />
|
||||||
|
<di:waypoint x="1230" y="240" />
|
||||||
|
<di:waypoint x="1300" y="240" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0p2r1bo_di" bpmnElement="Flow_0p2r1bo">
|
||||||
|
<di:waypoint x="1230" y="282" />
|
||||||
|
<di:waypoint x="1230" y="120" />
|
||||||
|
<di:waypoint x="1300" y="120" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1nbjr72_di" bpmnElement="Flow_1nbjr72">
|
||||||
|
<di:waypoint x="1150" y="307" />
|
||||||
|
<di:waypoint x="1205" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1eiud85_di" bpmnElement="Flow_1eiud85">
|
||||||
|
<di:waypoint x="995" y="307" />
|
||||||
|
<di:waypoint x="1050" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0zz2hbq_di" bpmnElement="Flow_0zz2hbq">
|
||||||
|
<di:waypoint x="890" y="510" />
|
||||||
|
<di:waypoint x="970" y="510" />
|
||||||
|
<di:waypoint x="970" y="332" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0ya8hw8_di" bpmnElement="Flow_0ya8hw8">
|
||||||
|
<di:waypoint x="710" y="332" />
|
||||||
|
<di:waypoint x="710" y="510" />
|
||||||
|
<di:waypoint x="790" y="510" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_12ie6w0_di" bpmnElement="Flow_12ie6w0">
|
||||||
|
<di:waypoint x="890" y="370" />
|
||||||
|
<di:waypoint x="970" y="370" />
|
||||||
|
<di:waypoint x="970" y="332" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_15zy1q7_di" bpmnElement="Flow_15zy1q7">
|
||||||
|
<di:waypoint x="710" y="332" />
|
||||||
|
<di:waypoint x="710" y="370" />
|
||||||
|
<di:waypoint x="790" y="370" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0tk64b6_di" bpmnElement="Flow_0tk64b6">
|
||||||
|
<di:waypoint x="890" y="110" />
|
||||||
|
<di:waypoint x="970" y="110" />
|
||||||
|
<di:waypoint x="970" y="282" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0qf2y84_di" bpmnElement="Flow_0qf2y84">
|
||||||
|
<di:waypoint x="710" y="282" />
|
||||||
|
<di:waypoint x="710" y="110" />
|
||||||
|
<di:waypoint x="790" y="110" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_16y8glw_di" bpmnElement="Flow_16y8glw">
|
||||||
|
<di:waypoint x="480" y="307" />
|
||||||
|
<di:waypoint x="530" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_16342pm_di" bpmnElement="Flow_16342pm">
|
||||||
|
<di:waypoint x="890" y="240" />
|
||||||
|
<di:waypoint x="970" y="240" />
|
||||||
|
<di:waypoint x="970" y="282" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_19xeq76_di" bpmnElement="Flow_19xeq76">
|
||||||
|
<di:waypoint x="710" y="282" />
|
||||||
|
<di:waypoint x="710" y="240" />
|
||||||
|
<di:waypoint x="790" y="240" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_1e2qi9s_di" bpmnElement="Flow_1e2qi9s">
|
||||||
|
<di:waypoint x="1770" y="307" />
|
||||||
|
<di:waypoint x="1800" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_05ja25w_di" bpmnElement="SequenceFlow_05ja25w">
|
||||||
|
<di:waypoint x="168" y="307" />
|
||||||
|
<di:waypoint x="230" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_0h50bp3_di" bpmnElement="SequenceFlow_0h50bp3">
|
||||||
|
<di:waypoint x="330" y="307" />
|
||||||
|
<di:waypoint x="380" y="307" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="132" y="289" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="ManualTask_1ofy9yz_di" bpmnElement="ManualTask_Instructions">
|
||||||
|
<dc:Bounds x="230" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="UserTask_0xdpoxl_di" bpmnElement="Activity-PI_Info">
|
||||||
|
<dc:Bounds x="380" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="UserTask_0ecab9j_di" bpmnElement="Personnel">
|
||||||
|
<dc:Bounds x="1050" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="UserTask_0l8vxty_di" bpmnElement="UserTask_CoreResource">
|
||||||
|
<dc:Bounds x="790" y="330" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_09wp7av_di" bpmnElement="EndEvent_09wp7av">
|
||||||
|
<dc:Bounds x="2692" y="289" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1mg5lp9_di" bpmnElement="Activity_AcknowledgePlanReview">
|
||||||
|
<dc:Bounds x="1670" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1xgrlzr_di" bpmnElement="Activity_SharedSpaceInfo">
|
||||||
|
<dc:Bounds x="790" y="200" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Gateway_0tn2il3_di" bpmnElement="Gateway_0frfdnc">
|
||||||
|
<dc:Bounds x="685" y="282" width="50" height="50" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Gateway_1o1fcbg_di" bpmnElement="Gateway_1vj4zd3">
|
||||||
|
<dc:Bounds x="945" y="282" width="50" height="50" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1jefdme_di" bpmnElement="Activity_ExclusiveSpace">
|
||||||
|
<dc:Bounds x="790" y="70" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0ysw6zo_di" bpmnElement="Activity_nonUVASpaces">
|
||||||
|
<dc:Bounds x="790" y="470" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1xag6qb_di" bpmnElement="Activity_DistanceReq">
|
||||||
|
<dc:Bounds x="1300" y="80" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Gateway_0yof76x_di" bpmnElement="Gateway_18jn18b">
|
||||||
|
<dc:Bounds x="1205" y="282" width="50" height="50" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_166b0qq_di" bpmnElement="Activity_PWA">
|
||||||
|
<dc:Bounds x="1300" y="200" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0byj9mp_di" bpmnElement="Activity_HSR">
|
||||||
|
<dc:Bounds x="1300" y="340" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0j0p13i_di" bpmnElement="Activity_OtherReq">
|
||||||
|
<dc:Bounds x="1300" y="470" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_1h30fo8_di" bpmnElement="Activity_SubmitPlan">
|
||||||
|
<dc:Bounds x="1930" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Gateway_03lxnzh_di" bpmnElement="Gateway_0sijkgx">
|
||||||
|
<dc:Bounds x="1445" y="282" width="50" height="50" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_117owwi_di" bpmnElement="Activity_1tub2mc">
|
||||||
|
<dc:Bounds x="1540" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0fxf44t_di" bpmnElement="Activity_ReviewStatus">
|
||||||
|
<dc:Bounds x="2230" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_080o38p_di" bpmnElement="Activity_ApprovalInfo">
|
||||||
|
<dc:Bounds x="1800" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0wuukfn_di" bpmnElement="Activity_1pklpot">
|
||||||
|
<dc:Bounds x="2520" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0js9ww9_di" bpmnElement="Activity_0u9ic8o">
|
||||||
|
<dc:Bounds x="2370" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0wnn9de_di" bpmnElement="Activity_0absozl">
|
||||||
|
<dc:Bounds x="2080" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Activity_0f0ak6p_di" bpmnElement="Activity_1u58hox">
|
||||||
|
<dc:Bounds x="530" y="267" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn:definitions>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1v1rp1q" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||||
|
<bpmn:process id="empty_workflow" isExecutable="true">
|
||||||
|
<bpmn:startEvent id="StartEvent_1">
|
||||||
|
<bpmn:outgoing>SequenceFlow_0lvudp8</bpmn:outgoing>
|
||||||
|
</bpmn:startEvent>
|
||||||
|
<bpmn:sequenceFlow id="SequenceFlow_0lvudp8" sourceRef="StartEvent_1" targetRef="EndEvent_0q4qzl9" />
|
||||||
|
<bpmn:endEvent id="EndEvent_0q4qzl9">
|
||||||
|
<bpmn:incoming>SequenceFlow_0lvudp8</bpmn:incoming>
|
||||||
|
</bpmn:endEvent>
|
||||||
|
</bpmn:process>
|
||||||
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="empty_workflow">
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_0lvudp8_di" bpmnElement="SequenceFlow_0lvudp8">
|
||||||
|
<di:waypoint x="238" y="117" />
|
||||||
|
<di:waypoint x="432" y="117" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_0q4qzl9_di" bpmnElement="EndEvent_0q4qzl9">
|
||||||
|
<dc:Bounds x="432" y="99" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="202" y="99" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</bpmn:definitions>
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1x1akiz" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1x1akiz" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||||
<bpmn:process id="Process_0quormc" isExecutable="true">
|
<bpmn:process id="Process_0quormc" isExecutable="true">
|
||||||
<bpmn:startEvent id="StartEvent_1">
|
<bpmn:startEvent id="StartEvent_1">
|
||||||
<bpmn:outgoing>SequenceFlow_17znkku</bpmn:outgoing>
|
<bpmn:outgoing>SequenceFlow_17znkku</bpmn:outgoing>
|
||||||
|
@ -16,10 +16,9 @@
|
||||||
</camunda:formField>
|
</camunda:formField>
|
||||||
<camunda:formField id="FormField_FromOSP" label="From OSP" type="autocomplete">
|
<camunda:formField id="FormField_FromOSP" label="From OSP" type="autocomplete">
|
||||||
<camunda:properties>
|
<camunda:properties>
|
||||||
<camunda:property id="enum.options.file" value="sponsors.xls" />
|
<camunda:property id="spreadsheet.name" value="sponsors.xls" />
|
||||||
<camunda:property id="enum.options.value.column" value="CUSTOMER_NUMBER" />
|
<camunda:property id="spreadsheet.value.column" value="CUSTOMER_NUMBER" />
|
||||||
<camunda:property id="enum.options.label.column" value="CUSTOMER_NAME" />
|
<camunda:property id="spreadsheet.label.column" value="CUSTOMER_NAME" />
|
||||||
<camunda:property id="enum.options.lookup" value="True" />
|
|
||||||
</camunda:properties>
|
</camunda:properties>
|
||||||
</camunda:formField>
|
</camunda:formField>
|
||||||
<camunda:formField id="FormField_Type" label="Select all that apply:" type="enum">
|
<camunda:formField id="FormField_Type" label="Select all that apply:" type="enum">
|
||||||
|
@ -70,51 +69,51 @@
|
||||||
</bpmn:process>
|
</bpmn:process>
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0quormc">
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0quormc">
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_1n3utyf_di" bpmnElement="SequenceFlow_1n3utyf">
|
<bpmndi:BPMNEdge id="Flow_1l3gw28_di" bpmnElement="Flow_1l3gw28">
|
||||||
<di:waypoint x="430" y="40" />
|
<di:waypoint x="430" y="280" />
|
||||||
<di:waypoint x="490" y="40" />
|
<di:waypoint x="490" y="280" />
|
||||||
<di:waypoint x="490" y="92" />
|
<di:waypoint x="490" y="222" />
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_17znkku_di" bpmnElement="SequenceFlow_17znkku">
|
|
||||||
<di:waypoint x="188" y="117" />
|
|
||||||
<di:waypoint x="235" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_13604n2_di" bpmnElement="Flow_13604n2">
|
|
||||||
<di:waypoint x="260" y="92" />
|
|
||||||
<di:waypoint x="260" y="40" />
|
|
||||||
<di:waypoint x="330" y="40" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_030v94s_di" bpmnElement="Flow_030v94s">
|
|
||||||
<di:waypoint x="515" y="117" />
|
|
||||||
<di:waypoint x="552" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_0hdjgx6_di" bpmnElement="Flow_0hdjgx6">
|
<bpmndi:BPMNEdge id="Flow_0hdjgx6_di" bpmnElement="Flow_0hdjgx6">
|
||||||
<di:waypoint x="260" y="142" />
|
<di:waypoint x="260" y="222" />
|
||||||
<di:waypoint x="260" y="200" />
|
<di:waypoint x="260" y="280" />
|
||||||
<di:waypoint x="330" y="200" />
|
<di:waypoint x="330" y="280" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_1l3gw28_di" bpmnElement="Flow_1l3gw28">
|
<bpmndi:BPMNEdge id="Flow_030v94s_di" bpmnElement="Flow_030v94s">
|
||||||
<di:waypoint x="430" y="200" />
|
<di:waypoint x="515" y="197" />
|
||||||
<di:waypoint x="490" y="200" />
|
<di:waypoint x="552" y="197" />
|
||||||
<di:waypoint x="490" y="142" />
|
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape id="EndEvent_19upzzo_di" bpmnElement="EndEvent_19upzzo">
|
<bpmndi:BPMNEdge id="Flow_13604n2_di" bpmnElement="Flow_13604n2">
|
||||||
<dc:Bounds x="552" y="99" width="36" height="36" />
|
<di:waypoint x="260" y="172" />
|
||||||
</bpmndi:BPMNShape>
|
<di:waypoint x="260" y="120" />
|
||||||
<bpmndi:BPMNShape id="Gateway_1s4ro2h_di" bpmnElement="Gateway_0bgimhg">
|
<di:waypoint x="330" y="120" />
|
||||||
<dc:Bounds x="235" y="92" width="50" height="50" />
|
</bpmndi:BPMNEdge>
|
||||||
</bpmndi:BPMNShape>
|
<bpmndi:BPMNEdge id="SequenceFlow_1n3utyf_di" bpmnElement="SequenceFlow_1n3utyf">
|
||||||
<bpmndi:BPMNShape id="Gateway_1kowkjp_di" bpmnElement="Gateway_1924s77">
|
<di:waypoint x="430" y="120" />
|
||||||
<dc:Bounds x="465" y="92" width="50" height="50" />
|
<di:waypoint x="490" y="120" />
|
||||||
|
<di:waypoint x="490" y="172" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_17znkku_di" bpmnElement="SequenceFlow_17znkku">
|
||||||
|
<di:waypoint x="188" y="197" />
|
||||||
|
<di:waypoint x="235" y="197" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
|
<dc:Bounds x="152" y="179" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="UserTask_15oiwqt_di" bpmnElement="Task_14cuhvm">
|
<bpmndi:BPMNShape id="UserTask_15oiwqt_di" bpmnElement="Task_14cuhvm">
|
||||||
<dc:Bounds x="330" y="0" width="100" height="80" />
|
<dc:Bounds x="330" y="80" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_19upzzo_di" bpmnElement="EndEvent_19upzzo">
|
||||||
|
<dc:Bounds x="552" y="179" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="Activity_1oeywwl_di" bpmnElement="Activity_0xxhfyh">
|
<bpmndi:BPMNShape id="Activity_1oeywwl_di" bpmnElement="Activity_0xxhfyh">
|
||||||
<dc:Bounds x="330" y="160" width="100" height="80" />
|
<dc:Bounds x="330" y="240" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
<bpmndi:BPMNShape id="Gateway_1s4ro2h_di" bpmnElement="Gateway_0bgimhg">
|
||||||
<dc:Bounds x="152" y="99" width="36" height="36" />
|
<dc:Bounds x="235" y="172" width="50" height="50" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Gateway_1kowkjp_di" bpmnElement="Gateway_1924s77">
|
||||||
|
<dc:Bounds x="465" y="172" width="50" height="50" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
</bpmndi:BPMNPlane>
|
</bpmndi:BPMNPlane>
|
||||||
</bpmndi:BPMNDiagram>
|
</bpmndi:BPMNDiagram>
|
||||||
|
|
Binary file not shown.
|
@ -1,23 +1,30 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# run migrations
|
# run migrations
|
||||||
export FLASK_APP=./crc/__init__.py
|
export FLASK_APP=/app/crc/__init__.py
|
||||||
|
|
||||||
for entry in ./instance/* ; do
|
|
||||||
echo "$entry"
|
|
||||||
cat $entry
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$DOWNGRADE_DB" = "true" ]; then
|
if [ "$DOWNGRADE_DB" = "true" ]; then
|
||||||
echo 'Downgrading...'
|
echo 'Downgrading database...'
|
||||||
pipenv run flask db downgrade
|
pipenv run flask db downgrade
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$UPGRADE_DB" = "true" ]; then
|
||||||
|
echo 'Upgrading database...'
|
||||||
pipenv run flask db upgrade
|
pipenv run flask db upgrade
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$RESET_DB" = "true" ]; then
|
if [ "$RESET_DB" = "true" ]; then
|
||||||
echo 'Resetting database...'
|
echo 'Resetting database and seeding it with example CR Connect data...'
|
||||||
pipenv run flask load-example-data
|
pipenv run flask load-example-data
|
||||||
fi
|
fi
|
||||||
|
|
||||||
pipenv run python ./run.py
|
if [ "$RESET_DB_RRT" = "true" ]; then
|
||||||
|
echo 'Resetting database and seeding it with example RRT data...'
|
||||||
|
pipenv run flask load-example-rrt-data
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$APPLICATION_ROOT" = "/" ]; then
|
||||||
|
pipenv run gunicorn --bind 0.0.0.0:$PORT0 wsgi:app
|
||||||
|
else
|
||||||
|
pipenv run gunicorn -e SCRIPT_NAME="$APPLICATION_ROOT" --bind 0.0.0.0:$PORT0 wsgi:app
|
||||||
|
fi
|
||||||
|
|
|
@ -14,6 +14,7 @@ class ExampleDataLoader:
|
||||||
session.flush() # Clear out any transactions before deleting it all to avoid spurious errors.
|
session.flush() # Clear out any transactions before deleting it all to avoid spurious errors.
|
||||||
for table in reversed(db.metadata.sorted_tables):
|
for table in reversed(db.metadata.sorted_tables):
|
||||||
session.execute(table.delete())
|
session.execute(table.delete())
|
||||||
|
session.commit()
|
||||||
session.flush()
|
session.flush()
|
||||||
|
|
||||||
def load_all(self):
|
def load_all(self):
|
||||||
|
@ -191,8 +192,68 @@ class ExampleDataLoader:
|
||||||
category_id=None,
|
category_id=None,
|
||||||
master_spec=True)
|
master_spec=True)
|
||||||
|
|
||||||
|
def load_rrt(self):
|
||||||
|
file_path = os.path.join(app.root_path, 'static', 'reference', 'rrt_documents.xlsx')
|
||||||
|
file = open(file_path, "rb")
|
||||||
|
FileService.add_reference_file(FileService.DOCUMENT_LIST,
|
||||||
|
binary_data=file.read(),
|
||||||
|
content_type=CONTENT_TYPES['xls'])
|
||||||
|
file.close()
|
||||||
|
|
||||||
def create_spec(self, id, name, display_name="", description="", filepath=None, master_spec=False, category_id=None, display_order=None):
|
category = WorkflowSpecCategoryModel(
|
||||||
|
id=0,
|
||||||
|
name='research_rampup_category',
|
||||||
|
display_name='Research Ramp-up Category',
|
||||||
|
display_order=0
|
||||||
|
)
|
||||||
|
db.session.add(category)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
self.create_spec(id="rrt_top_level_workflow",
|
||||||
|
name="rrt_top_level_workflow",
|
||||||
|
display_name="Top Level Workflow",
|
||||||
|
description="Does nothing, we don't use the master workflow here.",
|
||||||
|
category_id=None,
|
||||||
|
master_spec=True)
|
||||||
|
|
||||||
|
self.create_spec(id="research_rampup",
|
||||||
|
name="research_rampup",
|
||||||
|
display_name="Research Ramp-up Toolkit",
|
||||||
|
description="Process for creating a new research ramp-up request.",
|
||||||
|
category_id=0,
|
||||||
|
master_spec=False)
|
||||||
|
|
||||||
|
def load_test_data(self):
|
||||||
|
self.load_reference_documents()
|
||||||
|
|
||||||
|
category = WorkflowSpecCategoryModel(
|
||||||
|
id=0,
|
||||||
|
name='test_category',
|
||||||
|
display_name='Test Category',
|
||||||
|
display_order=0
|
||||||
|
)
|
||||||
|
db.session.add(category)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
self.create_spec(id="empty_workflow",
|
||||||
|
name="empty_workflow",
|
||||||
|
display_name="Top Level Workflow",
|
||||||
|
description="Does nothing, we don't use the master workflow here.",
|
||||||
|
category_id=None,
|
||||||
|
master_spec=True,
|
||||||
|
from_tests = True)
|
||||||
|
|
||||||
|
self.create_spec(id="random_fact",
|
||||||
|
name="random_fact",
|
||||||
|
display_name="Random Fact",
|
||||||
|
description="The workflow for a Random Fact.",
|
||||||
|
category_id=0,
|
||||||
|
master_spec=False,
|
||||||
|
from_tests=True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_spec(self, id, name, display_name="", description="", filepath=None, master_spec=False,
|
||||||
|
category_id=None, display_order=None, from_tests=False):
|
||||||
"""Assumes that a directory exists in static/bpmn with the same name as the given id.
|
"""Assumes that a directory exists in static/bpmn with the same name as the given id.
|
||||||
further assumes that the [id].bpmn is the primary file for the workflow.
|
further assumes that the [id].bpmn is the primary file for the workflow.
|
||||||
returns an array of data models to be added to the database."""
|
returns an array of data models to be added to the database."""
|
||||||
|
@ -207,8 +268,11 @@ class ExampleDataLoader:
|
||||||
display_order=display_order)
|
display_order=display_order)
|
||||||
db.session.add(spec)
|
db.session.add(spec)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
if not filepath:
|
if not filepath and not from_tests:
|
||||||
filepath = os.path.join(app.root_path, 'static', 'bpmn', id, "*")
|
filepath = os.path.join(app.root_path, 'static', 'bpmn', id, "*")
|
||||||
|
if not filepath and from_tests:
|
||||||
|
filepath = os.path.join(app.root_path, '..', 'tests', 'data', id, "*")
|
||||||
|
|
||||||
files = glob.glob(filepath)
|
files = glob.glob(filepath)
|
||||||
for file_path in files:
|
for file_path in files:
|
||||||
noise, file_extension = os.path.splitext(file_path)
|
noise, file_extension = os.path.splitext(file_path)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""empty message
|
"""empty message
|
||||||
|
|
||||||
Revision ID: dadf16305482
|
Revision ID: 55c6cd407d89
|
||||||
Revises: cc4bccc5e5a8
|
Revises: cc4bccc5e5a8
|
||||||
Create Date: 2020-05-23 20:43:43.882267
|
Create Date: 2020-05-22 22:02:46.650170
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
|
@ -10,7 +10,7 @@ import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = 'dadf16305482'
|
revision = '55c6cd407d89'
|
||||||
down_revision = 'cc4bccc5e5a8'
|
down_revision = 'cc4bccc5e5a8'
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
|
@ -22,30 +22,18 @@ def upgrade():
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('study_id', sa.Integer(), nullable=False),
|
sa.Column('study_id', sa.Integer(), nullable=False),
|
||||||
sa.Column('workflow_id', sa.Integer(), nullable=False),
|
sa.Column('workflow_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('workflow_version', sa.String(), nullable=True),
|
||||||
sa.Column('approver_uid', sa.String(), nullable=True),
|
sa.Column('approver_uid', sa.String(), nullable=True),
|
||||||
sa.Column('status', sa.String(), nullable=True),
|
sa.Column('status', sa.String(), nullable=True),
|
||||||
sa.Column('message', sa.String(), nullable=True),
|
sa.Column('message', sa.String(), nullable=True),
|
||||||
sa.Column('date_created', sa.DateTime(timezone=True), nullable=True),
|
|
||||||
sa.Column('version', sa.Integer(), nullable=True),
|
|
||||||
sa.Column('workflow_hash', sa.String(), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['study_id'], ['study.id'], ),
|
sa.ForeignKeyConstraint(['study_id'], ['study.id'], ),
|
||||||
sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ),
|
sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
)
|
)
|
||||||
op.create_table('approval_file',
|
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('file_id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('approval_id', sa.Integer(), nullable=False),
|
|
||||||
sa.Column('file_version', sa.Integer(), nullable=False),
|
|
||||||
sa.ForeignKeyConstraint(['approval_id'], ['approval.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['file_id'], ['file.id'], ),
|
|
||||||
sa.PrimaryKeyConstraint('id')
|
|
||||||
)
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.drop_table('approval_file')
|
|
||||||
op.drop_table('approval')
|
op.drop_table('approval')
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
|
@ -0,0 +1,44 @@
|
||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 9b43e725f39c
|
||||||
|
Revises: 55c6cd407d89
|
||||||
|
Create Date: 2020-05-25 23:09:14.761831
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '9b43e725f39c'
|
||||||
|
down_revision = '55c6cd407d89'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('approval_file',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('file_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('approval_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('file_version', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['approval_id'], ['approval.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['file_id'], ['file.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.add_column('approval', sa.Column('date_created', sa.DateTime(timezone=True), nullable=True))
|
||||||
|
op.add_column('approval', sa.Column('version', sa.Integer(), nullable=True))
|
||||||
|
op.add_column('approval', sa.Column('workflow_hash', sa.String(), nullable=True))
|
||||||
|
op.drop_column('approval', 'workflow_version')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('approval', sa.Column('workflow_version', sa.VARCHAR(), autoincrement=False, nullable=True))
|
||||||
|
op.drop_column('approval', 'workflow_hash')
|
||||||
|
op.drop_column('approval', 'version')
|
||||||
|
op.drop_column('approval', 'date_created')
|
||||||
|
op.drop_table('approval_file')
|
||||||
|
# ### end Alembic commands ###
|
|
@ -0,0 +1,3 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(setup_requires=["pbr"], pbr=True)
|
|
@ -1,6 +1,9 @@
|
||||||
# Set environment variable to testing before loading.
|
# Set environment variable to testing before loading.
|
||||||
# IMPORTANT - Environment must be loaded before app, models, etc....
|
# IMPORTANT - Environment must be loaded before app, models, etc....
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from sqlalchemy import Sequence
|
||||||
|
|
||||||
os.environ["TESTING"] = "true"
|
os.environ["TESTING"] = "true"
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -20,9 +23,8 @@ from crc import app, db, session
|
||||||
from example_data import ExampleDataLoader
|
from example_data import ExampleDataLoader
|
||||||
|
|
||||||
#UNCOMMENT THIS FOR DEBUGGING SQL ALCHEMY QUERIES
|
#UNCOMMENT THIS FOR DEBUGGING SQL ALCHEMY QUERIES
|
||||||
# import logging
|
import logging
|
||||||
# logging.basicConfig()
|
logging.basicConfig()
|
||||||
# logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTest(unittest.TestCase):
|
class BaseTest(unittest.TestCase):
|
||||||
|
@ -99,12 +101,10 @@ class BaseTest(unittest.TestCase):
|
||||||
def logged_in_headers(self, user=None, redirect_url='http://some/frontend/url'):
|
def logged_in_headers(self, user=None, redirect_url='http://some/frontend/url'):
|
||||||
if user is None:
|
if user is None:
|
||||||
uid = self.test_uid
|
uid = self.test_uid
|
||||||
user_info = {'uid': self.test_uid, 'first_name': 'Daniel', 'last_name': 'Funk',
|
user_info = {'uid': self.test_uid}
|
||||||
'email_address': 'dhf8r@virginia.edu'}
|
|
||||||
else:
|
else:
|
||||||
uid = user.uid
|
uid = user.uid
|
||||||
user_info = {'uid': user.uid, 'first_name': user.first_name, 'last_name': user.last_name,
|
user_info = {'uid': user.uid}
|
||||||
'email_address': user.email_address}
|
|
||||||
|
|
||||||
query_string = self.user_info_to_query_string(user_info, redirect_url)
|
query_string = self.user_info_to_query_string(user_info, redirect_url)
|
||||||
rv = self.app.get("/v1.0/sso_backdoor%s" % query_string, follow_redirects=False)
|
rv = self.app.get("/v1.0/sso_backdoor%s" % query_string, follow_redirects=False)
|
||||||
|
@ -115,10 +115,17 @@ class BaseTest(unittest.TestCase):
|
||||||
self.assertIsNotNone(user_model.display_name)
|
self.assertIsNotNone(user_model.display_name)
|
||||||
return dict(Authorization='Bearer ' + user_model.encode_auth_token().decode())
|
return dict(Authorization='Bearer ' + user_model.encode_auth_token().decode())
|
||||||
|
|
||||||
def load_example_data(self):
|
def load_example_data(self, use_crc_data=False):
|
||||||
|
"""use_crc_data will cause this to load the mammoth collection of documents
|
||||||
|
we built up developing crc, otherwise it depends on a small setup for
|
||||||
|
running tests."""
|
||||||
|
|
||||||
from example_data import ExampleDataLoader
|
from example_data import ExampleDataLoader
|
||||||
ExampleDataLoader.clean_db()
|
ExampleDataLoader.clean_db()
|
||||||
|
if(use_crc_data):
|
||||||
ExampleDataLoader().load_all()
|
ExampleDataLoader().load_all()
|
||||||
|
else:
|
||||||
|
ExampleDataLoader().load_test_data()
|
||||||
|
|
||||||
for user_json in self.users:
|
for user_json in self.users:
|
||||||
db.session.add(UserModel(**user_json))
|
db.session.add(UserModel(**user_json))
|
||||||
|
@ -127,6 +134,7 @@ class BaseTest(unittest.TestCase):
|
||||||
study_model = StudyModel(**study_json)
|
study_model = StudyModel(**study_json)
|
||||||
db.session.add(study_model)
|
db.session.add(study_model)
|
||||||
StudyService._add_all_workflow_specs_to_study(study_model)
|
StudyService._add_all_workflow_specs_to_study(study_model)
|
||||||
|
db.session.execute(Sequence(StudyModel.__tablename__ + '_id_seq'))
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
|
|
||||||
|
@ -189,7 +197,7 @@ class BaseTest(unittest.TestCase):
|
||||||
for key, value in items:
|
for key, value in items:
|
||||||
query_string_list.append('%s=%s' % (key, urllib.parse.quote(value)))
|
query_string_list.append('%s=%s' % (key, urllib.parse.quote(value)))
|
||||||
|
|
||||||
query_string_list.append('redirect_url=%s' % redirect_url)
|
query_string_list.append('redirect=%s' % redirect_url)
|
||||||
|
|
||||||
return '?%s' % '&'.join(query_string_list)
|
return '?%s' % '&'.join(query_string_list)
|
||||||
|
|
||||||
|
@ -225,12 +233,11 @@ class BaseTest(unittest.TestCase):
|
||||||
|
|
||||||
def create_workflow(self, workflow_name, study=None, category_id=None):
|
def create_workflow(self, workflow_name, study=None, category_id=None):
|
||||||
db.session.flush()
|
db.session.flush()
|
||||||
workflow = db.session.query(WorkflowSpecModel).filter(WorkflowSpecModel.name == workflow_name).first()
|
spec = db.session.query(WorkflowSpecModel).filter(WorkflowSpecModel.name == workflow_name).first()
|
||||||
if workflow:
|
if spec is None:
|
||||||
return workflow
|
spec = self.load_test_spec(workflow_name, category_id=category_id)
|
||||||
if study is None:
|
if study is None:
|
||||||
study = self.create_study()
|
study = self.create_study()
|
||||||
spec = self.load_test_spec(workflow_name, category_id=category_id)
|
|
||||||
workflow_model = StudyService._create_workflow_model(study, spec)
|
workflow_model = StudyService._create_workflow_model(study, spec)
|
||||||
return workflow_model
|
return workflow_model
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -1,6 +1,7 @@
|
||||||
|
from tests.base_test import BaseTest
|
||||||
|
|
||||||
from crc import db
|
from crc import db
|
||||||
from crc.models.user import UserModel
|
from crc.models.user import UserModel
|
||||||
from tests.base_test import BaseTest
|
|
||||||
|
|
||||||
|
|
||||||
class TestAuthentication(BaseTest):
|
class TestAuthentication(BaseTest):
|
||||||
|
@ -13,7 +14,7 @@ class TestAuthentication(BaseTest):
|
||||||
self.assertEqual("dhf8r", user.decode_auth_token(auth_token).get("sub"))
|
self.assertEqual("dhf8r", user.decode_auth_token(auth_token).get("sub"))
|
||||||
|
|
||||||
def test_backdoor_auth_creates_user(self):
|
def test_backdoor_auth_creates_user(self):
|
||||||
new_uid = 'czn1z';
|
new_uid = 'lb3dp' ## Assure this user id is in the fake responses from ldap.
|
||||||
self.load_example_data()
|
self.load_example_data()
|
||||||
user = db.session.query(UserModel).filter(UserModel.uid == new_uid).first()
|
user = db.session.query(UserModel).filter(UserModel.uid == new_uid).first()
|
||||||
self.assertIsNone(user)
|
self.assertIsNone(user)
|
||||||
|
@ -44,7 +45,7 @@ class TestAuthentication(BaseTest):
|
||||||
self.assertIsNone(user)
|
self.assertIsNone(user)
|
||||||
redirect_url = 'http://worlds.best.website/admin'
|
redirect_url = 'http://worlds.best.website/admin'
|
||||||
headers = dict(Uid=new_uid)
|
headers = dict(Uid=new_uid)
|
||||||
rv = self.app.get('login', follow_redirects=False, headers=headers)
|
rv = self.app.get('v1.0/login', follow_redirects=False, headers=headers)
|
||||||
self.assert_success(rv)
|
self.assert_success(rv)
|
||||||
user = db.session.query(UserModel).filter(UserModel.uid == new_uid).first()
|
user = db.session.query(UserModel).filter(UserModel.uid == new_uid).first()
|
||||||
self.assertIsNotNone(user)
|
self.assertIsNotNone(user)
|
||||||
|
@ -62,6 +63,7 @@ class TestAuthentication(BaseTest):
|
||||||
rv = self.app.get('/v1.0/user', headers=self.logged_in_headers())
|
rv = self.app.get('/v1.0/user', headers=self.logged_in_headers())
|
||||||
self.assert_success(rv)
|
self.assert_success(rv)
|
||||||
|
|
||||||
user = UserModel(uid="ajl2j", first_name='Aaron', last_name='Louie', email_address='ajl2j@virginia.edu')
|
# User must exist in the mock ldap responses.
|
||||||
|
user = UserModel(uid="dhf8r", first_name='Dan', last_name='Funk', email_address='dhf8r@virginia.edu')
|
||||||
rv = self.app.get('/v1.0/user', headers=self.logged_in_headers(user, redirect_url='http://omg.edu/lolwut'))
|
rv = self.app.get('/v1.0/user', headers=self.logged_in_headers(user, redirect_url='http://omg.edu/lolwut'))
|
||||||
self.assert_success(rv)
|
self.assert_success(rv)
|
||||||
|
|
|
@ -20,7 +20,7 @@ class TestFilesApi(BaseTest):
|
||||||
return (minimal_dbpm % content).encode()
|
return (minimal_dbpm % content).encode()
|
||||||
|
|
||||||
def test_list_files_for_workflow_spec(self):
|
def test_list_files_for_workflow_spec(self):
|
||||||
self.load_example_data()
|
self.load_example_data(use_crc_data=True)
|
||||||
spec_id = 'core_info'
|
spec_id = 'core_info'
|
||||||
spec = session.query(WorkflowSpecModel).filter_by(id=spec_id).first()
|
spec = session.query(WorkflowSpecModel).filter_by(id=spec_id).first()
|
||||||
rv = self.app.get('/v1.0/file?workflow_spec_id=%s' % spec_id,
|
rv = self.app.get('/v1.0/file?workflow_spec_id=%s' % spec_id,
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
|
|
||||||
|
from tests.base_test import BaseTest
|
||||||
|
|
||||||
from crc import session
|
from crc import session
|
||||||
from crc.models.file import FileDataModel, FileModel, LookupFileModel, LookupDataModel
|
from crc.models.file import FileDataModel, FileModel, LookupFileModel, LookupDataModel
|
||||||
from crc.services.file_service import FileService
|
from crc.services.file_service import FileService
|
||||||
from crc.services.lookup_service import LookupService
|
from crc.services.lookup_service import LookupService
|
||||||
from crc.services.workflow_processor import WorkflowProcessor
|
|
||||||
from crc.services.workflow_service import WorkflowService
|
|
||||||
from tests.base_test import BaseTest
|
|
||||||
|
|
||||||
|
|
||||||
class TestLookupService(BaseTest):
|
class TestLookupService(BaseTest):
|
||||||
|
|
||||||
|
|
||||||
def test_create_lookup_file_multiple_times_does_not_update_database(self):
|
def test_create_lookup_file_multiple_times_does_not_update_database(self):
|
||||||
spec = self.load_test_spec('enum_options_from_file')
|
spec = BaseTest.load_test_spec('enum_options_from_file')
|
||||||
file_model = session.query(FileModel).filter(FileModel.name == "customer_list.xls").first()
|
file_model = session.query(FileModel).filter(FileModel.name == "customer_list.xls").first()
|
||||||
file_data_model = session.query(FileDataModel).filter(FileDataModel.file_model == file_model).first()
|
file_data_model = session.query(FileDataModel).filter(FileDataModel.file_model == file_model).first()
|
||||||
|
|
||||||
LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NUMBER", "CUSTOMER_NAME")
|
LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NUMBER", "CUSTOMER_NAME")
|
||||||
LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NUMBER", "CUSTOMER_NAME")
|
LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NUMBER", "CUSTOMER_NAME")
|
||||||
LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NUMBER", "CUSTOMER_NAME")
|
LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NUMBER", "CUSTOMER_NAME")
|
||||||
|
@ -21,18 +23,16 @@ class TestLookupService(BaseTest):
|
||||||
self.assertEqual(1, len(lookup_records))
|
self.assertEqual(1, len(lookup_records))
|
||||||
lookup_record = lookup_records[0]
|
lookup_record = lookup_records[0]
|
||||||
lookup_data = session.query(LookupDataModel).filter(LookupDataModel.lookup_file_model == lookup_record).all()
|
lookup_data = session.query(LookupDataModel).filter(LookupDataModel.lookup_file_model == lookup_record).all()
|
||||||
self.assertEquals(19, len(lookup_data))
|
self.assertEquals(28, len(lookup_data))
|
||||||
# Using the same table with different lookup lable or value, does create additional records.
|
# Using the same table with different lookup lable or value, does create additional records.
|
||||||
LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NAME", "CUSTOMER_NUMBER")
|
LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NAME", "CUSTOMER_NUMBER")
|
||||||
lookup_records = session.query(LookupFileModel).all()
|
lookup_records = session.query(LookupFileModel).all()
|
||||||
self.assertIsNotNone(lookup_records)
|
self.assertIsNotNone(lookup_records)
|
||||||
self.assertEqual(2, len(lookup_records))
|
self.assertEqual(2, len(lookup_records))
|
||||||
FileService.delete_file(file_model.id) ## Assure we can delete the file.
|
|
||||||
|
|
||||||
def test_some_full_text_queries(self):
|
def test_some_full_text_queries(self):
|
||||||
self.load_test_spec('enum_options_from_file')
|
spec = BaseTest.load_test_spec('enum_options_from_file')
|
||||||
file_model = session.query(FileModel).filter(FileModel.name == "customer_list.xls").first()
|
file_model = session.query(FileModel).filter(FileModel.name == "customer_list.xls").first()
|
||||||
self.assertIsNotNone(file_model)
|
|
||||||
file_data_model = session.query(FileDataModel).filter(FileDataModel.file_model == file_model).first()
|
file_data_model = session.query(FileDataModel).filter(FileDataModel.file_model == file_model).first()
|
||||||
lookup_table = LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NUMBER", "CUSTOMER_NAME")
|
lookup_table = LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NUMBER", "CUSTOMER_NAME")
|
||||||
|
|
||||||
|
@ -51,8 +51,6 @@ class TestLookupService(BaseTest):
|
||||||
self.assertEquals(1, len(results), "case does not matter.")
|
self.assertEquals(1, len(results), "case does not matter.")
|
||||||
self.assertEquals("UVA - INTERNAL - GM USE ONLY", results[0].label)
|
self.assertEquals("UVA - INTERNAL - GM USE ONLY", results[0].label)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
results = LookupService._run_lookup_query(lookup_table, "medici", limit=10)
|
results = LookupService._run_lookup_query(lookup_table, "medici", limit=10)
|
||||||
self.assertEquals(1, len(results), "partial words are picked up.")
|
self.assertEquals(1, len(results), "partial words are picked up.")
|
||||||
self.assertEquals("The Medicines Company", results[0].label)
|
self.assertEquals("The Medicines Company", results[0].label)
|
||||||
|
@ -73,7 +71,29 @@ class TestLookupService(BaseTest):
|
||||||
self.assertEquals(7, len(results), "short terms get multiple correct results.")
|
self.assertEquals(7, len(results), "short terms get multiple correct results.")
|
||||||
self.assertEquals("Genetics Savings & Clone, Inc.", results[0].label)
|
self.assertEquals("Genetics Savings & Clone, Inc.", results[0].label)
|
||||||
|
|
||||||
|
results = LookupService._run_lookup_query(lookup_table, "reaction design", limit=10)
|
||||||
|
self.assertEquals(5, len(results), "all results come back for two terms.")
|
||||||
|
self.assertEquals("Reaction Design", results[0].label, "Exact matches come first.")
|
||||||
|
|
||||||
|
def test_prefer_exact_match(self):
|
||||||
|
spec = BaseTest.load_test_spec('enum_options_from_file')
|
||||||
|
file_model = session.query(FileModel).filter(FileModel.name == "customer_list.xls").first()
|
||||||
|
file_data_model = session.query(FileDataModel).filter(FileDataModel.file_model == file_model).first()
|
||||||
|
|
||||||
|
lookup_table = LookupService.get_lookup_table_from_data_model(file_data_model, "CUSTOMER_NUMBER",
|
||||||
|
"CUSTOMER_NAME")
|
||||||
|
results = LookupService._run_lookup_query(lookup_table, "1 Something", limit=10)
|
||||||
|
self.assertEquals("1 Something", results[0].label, "Exact matches are prefered")
|
||||||
|
|
||||||
|
|
||||||
|
# 1018 10000 Something Industry
|
||||||
|
# 1019 1000 Something Industry
|
||||||
|
# 1020 1 Something Industry
|
||||||
|
# 1021 10 Something Industry
|
||||||
|
# 1022 10000 Something Industry
|
||||||
|
|
||||||
# Fixme: Stop words are taken into account on the query side, and haven't found a fix yet.
|
# Fixme: Stop words are taken into account on the query side, and haven't found a fix yet.
|
||||||
#results = WorkflowService.run_lookup_query(lookup_table.id, "in", limit=10)
|
#results = WorkflowService.run_lookup_query(lookup_table.id, "in", limit=10)
|
||||||
#self.assertEquals(7, len(results), "stop words are not removed.")
|
#self.assertEquals(7, len(results), "stop words are not removed.")
|
||||||
#self.assertEquals("Genetics Savings & Clone, Inc.", results[0].label)
|
#self.assertEquals("Genetics Savings & Clone, Inc.", results[0].label)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from crc import app
|
||||||
from tests.base_test import BaseTest
|
from tests.base_test import BaseTest
|
||||||
from crc.services.protocol_builder import ProtocolBuilderService
|
from crc.services.protocol_builder import ProtocolBuilderService
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ class TestProtocolBuilder(BaseTest):
|
||||||
|
|
||||||
@patch('crc.services.protocol_builder.requests.get')
|
@patch('crc.services.protocol_builder.requests.get')
|
||||||
def test_get_studies(self, mock_get):
|
def test_get_studies(self, mock_get):
|
||||||
ProtocolBuilderService.ENABLED = True
|
app.config['PB_ENABLED'] = True
|
||||||
mock_get.return_value.ok = True
|
mock_get.return_value.ok = True
|
||||||
mock_get.return_value.text = self.protocol_builder_response('user_studies.json')
|
mock_get.return_value.text = self.protocol_builder_response('user_studies.json')
|
||||||
response = ProtocolBuilderService.get_studies(self.test_uid)
|
response = ProtocolBuilderService.get_studies(self.test_uid)
|
||||||
|
@ -18,7 +19,7 @@ class TestProtocolBuilder(BaseTest):
|
||||||
|
|
||||||
@patch('crc.services.protocol_builder.requests.get')
|
@patch('crc.services.protocol_builder.requests.get')
|
||||||
def test_get_investigators(self, mock_get):
|
def test_get_investigators(self, mock_get):
|
||||||
ProtocolBuilderService.ENABLED = True
|
app.config['PB_ENABLED'] = True
|
||||||
mock_get.return_value.ok = True
|
mock_get.return_value.ok = True
|
||||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||||
response = ProtocolBuilderService.get_investigators(self.test_study_id)
|
response = ProtocolBuilderService.get_investigators(self.test_study_id)
|
||||||
|
@ -30,7 +31,7 @@ class TestProtocolBuilder(BaseTest):
|
||||||
|
|
||||||
@patch('crc.services.protocol_builder.requests.get')
|
@patch('crc.services.protocol_builder.requests.get')
|
||||||
def test_get_required_docs(self, mock_get):
|
def test_get_required_docs(self, mock_get):
|
||||||
ProtocolBuilderService.ENABLED = True
|
app.config['PB_ENABLED'] = True
|
||||||
mock_get.return_value.ok = True
|
mock_get.return_value.ok = True
|
||||||
mock_get.return_value.text = self.protocol_builder_response('required_docs.json')
|
mock_get.return_value.text = self.protocol_builder_response('required_docs.json')
|
||||||
response = ProtocolBuilderService.get_required_docs(self.test_study_id)
|
response = ProtocolBuilderService.get_required_docs(self.test_study_id)
|
||||||
|
@ -40,7 +41,7 @@ class TestProtocolBuilder(BaseTest):
|
||||||
|
|
||||||
@patch('crc.services.protocol_builder.requests.get')
|
@patch('crc.services.protocol_builder.requests.get')
|
||||||
def test_get_details(self, mock_get):
|
def test_get_details(self, mock_get):
|
||||||
ProtocolBuilderService.ENABLED = True
|
app.config['PB_ENABLED'] = True
|
||||||
mock_get.return_value.ok = True
|
mock_get.return_value.ok = True
|
||||||
mock_get.return_value.text = self.protocol_builder_response('study_details.json')
|
mock_get.return_value.text = self.protocol_builder_response('study_details.json')
|
||||||
response = ProtocolBuilderService.get_study_details(self.test_study_id)
|
response = ProtocolBuilderService.get_study_details(self.test_study_id)
|
||||||
|
|
|
@ -16,10 +16,10 @@ class TestRequestApprovalScript(BaseTest):
|
||||||
workflow = self.create_workflow('empty_workflow')
|
workflow = self.create_workflow('empty_workflow')
|
||||||
processor = WorkflowProcessor(workflow)
|
processor = WorkflowProcessor(workflow)
|
||||||
task = processor.next_task()
|
task = processor.next_task()
|
||||||
task.data = {"approvals": ['dhf8r', 'lb3dp']}
|
task.data = {"study": {"approval1": "dhf8r", 'approval2':'lb3dp'}}
|
||||||
|
|
||||||
script = RequestApproval()
|
script = RequestApproval()
|
||||||
script.do_task(task, workflow.study_id, workflow.id, "approvals")
|
script.do_task(task, workflow.study_id, workflow.id, "study.approval1", "study.approval2")
|
||||||
self.assertEquals(2, db.session.query(ApprovalModel).count())
|
self.assertEquals(2, db.session.query(ApprovalModel).count())
|
||||||
|
|
||||||
def test_do_task_with_incorrect_argument(self):
|
def test_do_task_with_incorrect_argument(self):
|
||||||
|
@ -29,7 +29,7 @@ class TestRequestApprovalScript(BaseTest):
|
||||||
workflow = self.create_workflow('empty_workflow')
|
workflow = self.create_workflow('empty_workflow')
|
||||||
processor = WorkflowProcessor(workflow)
|
processor = WorkflowProcessor(workflow)
|
||||||
task = processor.next_task()
|
task = processor.next_task()
|
||||||
task.data = {"approvals": {'dhf8r':"invalid", 'lb3dp':"invalid"}}
|
task.data = {"approvals": {'dhf8r':["invalid"], 'lb3dp':"invalid"}}
|
||||||
script = RequestApproval()
|
script = RequestApproval()
|
||||||
with self.assertRaises(ApiError):
|
with self.assertRaises(ApiError):
|
||||||
script.do_task(task, workflow.study_id, workflow.id, "approvals")
|
script.do_task(task, workflow.study_id, workflow.id, "approvals")
|
||||||
|
@ -40,9 +40,9 @@ class TestRequestApprovalScript(BaseTest):
|
||||||
workflow = self.create_workflow('empty_workflow')
|
workflow = self.create_workflow('empty_workflow')
|
||||||
processor = WorkflowProcessor(workflow)
|
processor = WorkflowProcessor(workflow)
|
||||||
task = processor.next_task()
|
task = processor.next_task()
|
||||||
task.data = {"approvals": ['dhf8r', 'lb3dp']}
|
task.data = {"study": {"approval1": "dhf8r", 'approval2':'lb3dp'}}
|
||||||
|
|
||||||
script = RequestApproval()
|
script = RequestApproval()
|
||||||
script.do_task_validate_only(task, workflow.study_id, workflow.id, "approvals")
|
script.do_task_validate_only(task, workflow.study_id, workflow.id, "study.approval1")
|
||||||
self.assertEquals(0, db.session.query(ApprovalModel).count())
|
self.assertEquals(0, db.session.query(ApprovalModel).count())
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from tests.base_test import BaseTest
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crc import session
|
from crc import session, app
|
||||||
from crc.models.protocol_builder import ProtocolBuilderStatus, \
|
from crc.models.protocol_builder import ProtocolBuilderStatus, \
|
||||||
ProtocolBuilderStudySchema
|
ProtocolBuilderStudySchema
|
||||||
from crc.models.stats import TaskEventModel
|
from crc.models.stats import TaskEventModel
|
||||||
|
@ -15,14 +15,11 @@ from crc.services.protocol_builder import ProtocolBuilderService
|
||||||
class TestStudyApi(BaseTest):
|
class TestStudyApi(BaseTest):
|
||||||
|
|
||||||
TEST_STUDY = {
|
TEST_STUDY = {
|
||||||
"id": 12345,
|
|
||||||
"title": "Phase III Trial of Genuine People Personalities (GPP) Autonomous Intelligent Emotional Agents "
|
"title": "Phase III Trial of Genuine People Personalities (GPP) Autonomous Intelligent Emotional Agents "
|
||||||
"for Interstellar Spacecraft",
|
"for Interstellar Spacecraft",
|
||||||
"last_updated": datetime.now(tz=timezone.utc),
|
"last_updated": datetime.now(tz=timezone.utc),
|
||||||
"protocol_builder_status": ProtocolBuilderStatus.ACTIVE,
|
"protocol_builder_status": ProtocolBuilderStatus.ACTIVE,
|
||||||
"primary_investigator_id": "tricia.marie.mcmillan@heartofgold.edu",
|
"primary_investigator_id": "tmm2x",
|
||||||
"sponsor": "Sirius Cybernetics Corporation",
|
|
||||||
"ind_number": "567890",
|
|
||||||
"user_uid": "dhf8r",
|
"user_uid": "dhf8r",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,17 +42,9 @@ class TestStudyApi(BaseTest):
|
||||||
|
|
||||||
"""NOTE: The protocol builder is not enabled or mocked out. As the master workflow (which is empty),
|
"""NOTE: The protocol builder is not enabled or mocked out. As the master workflow (which is empty),
|
||||||
and the test workflow do not need it, and it is disabled in the configuration."""
|
and the test workflow do not need it, and it is disabled in the configuration."""
|
||||||
|
self.load_example_data()
|
||||||
new_study = self.add_test_study()
|
new_study = self.add_test_study()
|
||||||
new_study = session.query(StudyModel).filter_by(id=new_study["id"]).first()
|
new_study = session.query(StudyModel).filter_by(id=new_study["id"]).first()
|
||||||
# Add a category
|
|
||||||
new_category = WorkflowSpecCategoryModel(id=21, name="test_cat", display_name="Test Category", display_order=0)
|
|
||||||
session.add(new_category)
|
|
||||||
session.commit()
|
|
||||||
# Create a workflow specification
|
|
||||||
self.create_workflow("random_fact", study=new_study, category_id=new_category.id)
|
|
||||||
# Assure there is a master specification, and it has the lookup files it needs.
|
|
||||||
spec = self.load_test_spec("empty_workflow", master_spec=True)
|
|
||||||
self.create_reference_document()
|
|
||||||
|
|
||||||
api_response = self.app.get('/v1.0/study/%i' % new_study.id,
|
api_response = self.app.get('/v1.0/study/%i' % new_study.id,
|
||||||
headers=self.logged_in_headers(), content_type="application/json")
|
headers=self.logged_in_headers(), content_type="application/json")
|
||||||
|
@ -64,13 +53,12 @@ class TestStudyApi(BaseTest):
|
||||||
|
|
||||||
self.assertEqual(study.title, self.TEST_STUDY['title'])
|
self.assertEqual(study.title, self.TEST_STUDY['title'])
|
||||||
self.assertEqual(study.primary_investigator_id, self.TEST_STUDY['primary_investigator_id'])
|
self.assertEqual(study.primary_investigator_id, self.TEST_STUDY['primary_investigator_id'])
|
||||||
self.assertEqual(study.sponsor, self.TEST_STUDY['sponsor'])
|
|
||||||
self.assertEqual(study.ind_number, self.TEST_STUDY['ind_number'])
|
|
||||||
self.assertEqual(study.user_uid, self.TEST_STUDY['user_uid'])
|
self.assertEqual(study.user_uid, self.TEST_STUDY['user_uid'])
|
||||||
|
|
||||||
# Categories are read only, so switching to sub-scripting here.
|
# Categories are read only, so switching to sub-scripting here.
|
||||||
category = [c for c in study.categories if c['name'] == "test_cat"][0]
|
# This assumes there is one test category set up in the example data.
|
||||||
self.assertEqual("test_cat", category['name'])
|
category = study.categories[0]
|
||||||
|
self.assertEqual("test_category", category['name'])
|
||||||
self.assertEqual("Test Category", category['display_name'])
|
self.assertEqual("Test Category", category['display_name'])
|
||||||
self.assertEqual(1, len(category["workflows"]))
|
self.assertEqual(1, len(category["workflows"]))
|
||||||
workflow = category["workflows"][0]
|
workflow = category["workflows"][0]
|
||||||
|
@ -83,7 +71,7 @@ class TestStudyApi(BaseTest):
|
||||||
def test_add_study(self):
|
def test_add_study(self):
|
||||||
self.load_example_data()
|
self.load_example_data()
|
||||||
study = self.add_test_study()
|
study = self.add_test_study()
|
||||||
db_study = session.query(StudyModel).filter_by(id=12345).first()
|
db_study = session.query(StudyModel).filter_by(id=study['id']).first()
|
||||||
self.assertIsNotNone(db_study)
|
self.assertIsNotNone(db_study)
|
||||||
self.assertEqual(study["title"], db_study.title)
|
self.assertEqual(study["title"], db_study.title)
|
||||||
self.assertEqual(study["primary_investigator_id"], db_study.primary_investigator_id)
|
self.assertEqual(study["primary_investigator_id"], db_study.primary_investigator_id)
|
||||||
|
@ -92,7 +80,7 @@ class TestStudyApi(BaseTest):
|
||||||
self.assertEqual(study["user_uid"], db_study.user_uid)
|
self.assertEqual(study["user_uid"], db_study.user_uid)
|
||||||
|
|
||||||
workflow_spec_count =session.query(WorkflowSpecModel).filter(WorkflowSpecModel.is_master_spec == False).count()
|
workflow_spec_count =session.query(WorkflowSpecModel).filter(WorkflowSpecModel.is_master_spec == False).count()
|
||||||
workflow_count = session.query(WorkflowModel).filter(WorkflowModel.study_id == 12345).count()
|
workflow_count = session.query(WorkflowModel).filter(WorkflowModel.study_id == study['id']).count()
|
||||||
error_count = len(study["errors"])
|
error_count = len(study["errors"])
|
||||||
self.assertEqual(workflow_spec_count, workflow_count + error_count)
|
self.assertEqual(workflow_spec_count, workflow_count + error_count)
|
||||||
|
|
||||||
|
@ -117,7 +105,7 @@ class TestStudyApi(BaseTest):
|
||||||
def test_get_all_studies(self, mock_studies, mock_details, mock_docs, mock_investigators):
|
def test_get_all_studies(self, mock_studies, mock_details, mock_docs, mock_investigators):
|
||||||
# Enable the protocol builder for these tests, as the master_workflow and other workflows
|
# Enable the protocol builder for these tests, as the master_workflow and other workflows
|
||||||
# depend on using the PB for data.
|
# depend on using the PB for data.
|
||||||
ProtocolBuilderService.ENABLED = True
|
app.config['PB_ENABLED'] = True
|
||||||
self.load_example_data()
|
self.load_example_data()
|
||||||
s = StudyModel(
|
s = StudyModel(
|
||||||
id=54321, # This matches one of the ids from the study_details_json data.
|
id=54321, # This matches one of the ids from the study_details_json data.
|
||||||
|
@ -209,20 +197,13 @@ class TestStudyApi(BaseTest):
|
||||||
|
|
||||||
def test_delete_study_with_workflow_and_status(self):
|
def test_delete_study_with_workflow_and_status(self):
|
||||||
self.load_example_data()
|
self.load_example_data()
|
||||||
study = session.query(StudyModel).first()
|
workflow = session.query(WorkflowModel).first()
|
||||||
new_category = WorkflowSpecCategoryModel(id=21, name="test_cat", display_name="Test Category", display_order=0)
|
stats2 = TaskEventModel(study_id=workflow.study_id, workflow_id=workflow.id, user_uid=self.users[0]['uid'])
|
||||||
session.add(new_category)
|
|
||||||
session.commit()
|
|
||||||
# Create a workflow specification, and complete some stuff that would log stats
|
|
||||||
workflow = self.create_workflow("random_fact", study=study, category_id=new_category.id)
|
|
||||||
session.add(workflow)
|
|
||||||
session.commit()
|
|
||||||
stats2 = TaskEventModel(study_id=study.id, workflow_id=workflow.id, user_uid=self.users[0]['uid'])
|
|
||||||
session.add(stats2)
|
session.add(stats2)
|
||||||
session.commit()
|
session.commit()
|
||||||
rv = self.app.delete('/v1.0/study/%i' % study.id, headers=self.logged_in_headers())
|
rv = self.app.delete('/v1.0/study/%i' % workflow.study_id, headers=self.logged_in_headers())
|
||||||
self.assert_success(rv)
|
self.assert_success(rv)
|
||||||
del_study = session.query(StudyModel).filter(StudyModel.id == study.id).first()
|
del_study = session.query(StudyModel).filter(StudyModel.id == workflow.study_id).first()
|
||||||
self.assertIsNone(del_study)
|
self.assertIsNone(del_study)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crc import db
|
from crc import db, app
|
||||||
from crc.models.protocol_builder import ProtocolBuilderStatus
|
from crc.models.protocol_builder import ProtocolBuilderStatus
|
||||||
from crc.models.study import StudyModel
|
from crc.models.study import StudyModel
|
||||||
from crc.models.user import UserModel
|
from crc.models.user import UserModel
|
||||||
|
@ -57,7 +57,7 @@ class TestStudyService(BaseTest):
|
||||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||||
def test_total_tasks_updated(self, mock_docs):
|
def test_total_tasks_updated(self, mock_docs):
|
||||||
"""Assure that as a users progress is available when getting a list of studies for that user."""
|
"""Assure that as a users progress is available when getting a list of studies for that user."""
|
||||||
|
app.config['PB_ENABLED'] = True
|
||||||
docs_response = self.protocol_builder_response('required_docs.json')
|
docs_response = self.protocol_builder_response('required_docs.json')
|
||||||
mock_docs.return_value = json.loads(docs_response)
|
mock_docs.return_value = json.loads(docs_response)
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ class TestStudyService(BaseTest):
|
||||||
|
|
||||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||||
def test_get_required_docs(self, mock_docs):
|
def test_get_required_docs(self, mock_docs):
|
||||||
|
app.config['PB_ENABLED'] = True
|
||||||
# mock out the protocol builder
|
# mock out the protocol builder
|
||||||
docs_response = self.protocol_builder_response('required_docs.json')
|
docs_response = self.protocol_builder_response('required_docs.json')
|
||||||
mock_docs.return_value = json.loads(docs_response)
|
mock_docs.return_value = json.loads(docs_response)
|
||||||
|
|
|
@ -3,6 +3,8 @@ import os
|
||||||
import random
|
import random
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from tests.base_test import BaseTest
|
||||||
|
|
||||||
from crc import session, app
|
from crc import session, app
|
||||||
from crc.models.api_models import WorkflowApiSchema, MultiInstanceType, TaskSchema
|
from crc.models.api_models import WorkflowApiSchema, MultiInstanceType, TaskSchema
|
||||||
from crc.models.file import FileModelSchema
|
from crc.models.file import FileModelSchema
|
||||||
|
@ -10,7 +12,6 @@ from crc.models.stats import TaskEventModel
|
||||||
from crc.models.workflow import WorkflowStatus
|
from crc.models.workflow import WorkflowStatus
|
||||||
from crc.services.protocol_builder import ProtocolBuilderService
|
from crc.services.protocol_builder import ProtocolBuilderService
|
||||||
from crc.services.workflow_service import WorkflowService
|
from crc.services.workflow_service import WorkflowService
|
||||||
from tests.base_test import BaseTest
|
|
||||||
|
|
||||||
|
|
||||||
class TestTasksApi(BaseTest):
|
class TestTasksApi(BaseTest):
|
||||||
|
@ -303,14 +304,16 @@ class TestTasksApi(BaseTest):
|
||||||
|
|
||||||
@patch('crc.services.protocol_builder.requests.get')
|
@patch('crc.services.protocol_builder.requests.get')
|
||||||
def test_multi_instance_task(self, mock_get):
|
def test_multi_instance_task(self, mock_get):
|
||||||
|
|
||||||
|
self.load_example_data()
|
||||||
|
|
||||||
# Enable the protocol builder.
|
# Enable the protocol builder.
|
||||||
ProtocolBuilderService.ENABLED = True
|
app.config['PB_ENABLED'] = True
|
||||||
|
|
||||||
# This depends on getting a list of investigators back from the protocol builder.
|
# This depends on getting a list of investigators back from the protocol builder.
|
||||||
mock_get.return_value.ok = True
|
mock_get.return_value.ok = True
|
||||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||||
|
|
||||||
self.load_example_data()
|
|
||||||
workflow = self.create_workflow('multi_instance')
|
workflow = self.create_workflow('multi_instance')
|
||||||
|
|
||||||
# get the first form in the two form workflow.
|
# get the first form in the two form workflow.
|
||||||
|
@ -423,11 +426,12 @@ class TestTasksApi(BaseTest):
|
||||||
def test_parallel_multi_instance(self, mock_get):
|
def test_parallel_multi_instance(self, mock_get):
|
||||||
|
|
||||||
# Assure we get nine investigators back from the API Call, as set in the investigators.json file.
|
# Assure we get nine investigators back from the API Call, as set in the investigators.json file.
|
||||||
|
app.config['PB_ENABLED'] = True
|
||||||
mock_get.return_value.ok = True
|
mock_get.return_value.ok = True
|
||||||
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
mock_get.return_value.text = self.protocol_builder_response('investigators.json')
|
||||||
|
|
||||||
|
|
||||||
self.load_example_data()
|
self.load_example_data()
|
||||||
|
|
||||||
workflow = self.create_workflow('multi_instance_parallel')
|
workflow = self.create_workflow('multi_instance_parallel')
|
||||||
|
|
||||||
workflow_api = self.get_workflow_api(workflow)
|
workflow_api = self.get_workflow_api(workflow)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
from tests.base_test import BaseTest
|
||||||
|
|
||||||
|
from crc.scripts.update_study import UpdateStudy
|
||||||
|
from crc.services.workflow_processor import WorkflowProcessor
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpdateStudyScript(BaseTest):
|
||||||
|
|
||||||
|
def test_do_task(self):
|
||||||
|
self.load_example_data()
|
||||||
|
self.create_reference_document()
|
||||||
|
workflow = self.create_workflow('empty_workflow')
|
||||||
|
processor = WorkflowProcessor(workflow)
|
||||||
|
task = processor.next_task()
|
||||||
|
task.data = {"details": {
|
||||||
|
"label": "My New Title",
|
||||||
|
"value": "dhf8r"}
|
||||||
|
}
|
||||||
|
|
||||||
|
script = UpdateStudy()
|
||||||
|
script.do_task(task, workflow.study_id, workflow.id, "title:details.label", "pi:details.value")
|
||||||
|
self.assertEquals("My New Title", workflow.study.title)
|
||||||
|
self.assertEquals("dhf8r", workflow.study.primary_investigator_id)
|
|
@ -1,34 +1,31 @@
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import json
|
|
||||||
import string
|
|
||||||
import random
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from SpiffWorkflow import Task as SpiffTask
|
from tests.base_test import BaseTest
|
||||||
|
|
||||||
from SpiffWorkflow.bpmn.specs.EndEvent import EndEvent
|
from SpiffWorkflow.bpmn.specs.EndEvent import EndEvent
|
||||||
from SpiffWorkflow.camunda.specs.UserTask import Form, FormField
|
from SpiffWorkflow.camunda.specs.UserTask import FormField
|
||||||
from SpiffWorkflow.specs import TaskSpec
|
|
||||||
|
|
||||||
from crc import session, db, app
|
from crc import session, db, app
|
||||||
from crc.api.common import ApiError
|
from crc.api.common import ApiError
|
||||||
from crc.models.file import FileModel, FileDataModel, CONTENT_TYPES
|
from crc.models.file import FileModel, FileDataModel
|
||||||
|
from crc.models.protocol_builder import ProtocolBuilderStudySchema
|
||||||
|
from crc.services.protocol_builder import ProtocolBuilderService
|
||||||
from crc.models.study import StudyModel
|
from crc.models.study import StudyModel
|
||||||
from crc.models.workflow import WorkflowSpecModel, WorkflowStatus, WorkflowModel
|
from crc.models.workflow import WorkflowSpecModel, WorkflowStatus
|
||||||
from crc.services.file_service import FileService
|
from crc.services.file_service import FileService
|
||||||
from crc.services.study_service import StudyService
|
from crc.services.study_service import StudyService
|
||||||
from crc.models.protocol_builder import ProtocolBuilderStudySchema, ProtocolBuilderInvestigatorSchema, \
|
|
||||||
ProtocolBuilderRequiredDocumentSchema
|
|
||||||
from crc.services.workflow_service import WorkflowService
|
|
||||||
from tests.base_test import BaseTest
|
|
||||||
from crc.services.workflow_processor import WorkflowProcessor
|
from crc.services.workflow_processor import WorkflowProcessor
|
||||||
|
from crc.services.workflow_service import WorkflowService
|
||||||
|
|
||||||
|
|
||||||
class TestWorkflowProcessor(BaseTest):
|
class TestWorkflowProcessor(BaseTest):
|
||||||
|
|
||||||
def _populate_form_with_random_data(self, task):
|
def _populate_form_with_random_data(self, task):
|
||||||
api_task = WorkflowService.spiff_task_to_api_task(task, add_docs_and_forms=True)
|
api_task = WorkflowService.spiff_task_to_api_task(task, add_docs_and_forms=True)
|
||||||
WorkflowProcessor.populate_form_with_random_data(task, api_task)
|
WorkflowService.populate_form_with_random_data(task, api_task)
|
||||||
|
|
||||||
def get_processor(self, study_model, spec_model):
|
def get_processor(self, study_model, spec_model):
|
||||||
workflow_model = StudyService._create_workflow_model(study_model, spec_model)
|
workflow_model = StudyService._create_workflow_model(study_model, spec_model)
|
||||||
|
@ -361,7 +358,7 @@ class TestWorkflowProcessor(BaseTest):
|
||||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators')
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_investigators')
|
||||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs')
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs')
|
||||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details')
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details')
|
||||||
def test_master_bpmn(self, mock_details, mock_required_docs, mock_investigators, mock_studies):
|
def test_master_bpmn_for_crc(self, mock_details, mock_required_docs, mock_investigators, mock_studies):
|
||||||
|
|
||||||
# Mock Protocol Builder response
|
# Mock Protocol Builder response
|
||||||
studies_response = self.protocol_builder_response('user_studies.json')
|
studies_response = self.protocol_builder_response('user_studies.json')
|
||||||
|
@ -376,7 +373,8 @@ class TestWorkflowProcessor(BaseTest):
|
||||||
details_response = self.protocol_builder_response('study_details.json')
|
details_response = self.protocol_builder_response('study_details.json')
|
||||||
mock_details.return_value = json.loads(details_response)
|
mock_details.return_value = json.loads(details_response)
|
||||||
|
|
||||||
self.load_example_data()
|
self.load_example_data(use_crc_data=True)
|
||||||
|
app.config['PB_ENABLED'] = True
|
||||||
|
|
||||||
study = session.query(StudyModel).first()
|
study = session.query(StudyModel).first()
|
||||||
workflow_spec_model = db.session.query(WorkflowSpecModel).\
|
workflow_spec_model = db.session.query(WorkflowSpecModel).\
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
from tests.base_test import BaseTest
|
||||||
from crc import session
|
from crc import session
|
||||||
from crc.models.file import FileDataModel, FileModel, LookupFileModel, LookupDataModel
|
from crc.models.file import FileDataModel, FileModel, LookupFileModel, LookupDataModel
|
||||||
from crc.services.file_service import FileService
|
|
||||||
from crc.services.lookup_service import LookupService
|
from crc.services.lookup_service import LookupService
|
||||||
from crc.services.workflow_processor import WorkflowProcessor
|
from crc.services.workflow_processor import WorkflowProcessor
|
||||||
from crc.services.workflow_service import WorkflowService
|
from crc.services.workflow_service import WorkflowService
|
||||||
from tests.base_test import BaseTest
|
|
||||||
|
|
||||||
|
|
||||||
class TestWorkflowService(BaseTest):
|
class TestWorkflowService(BaseTest):
|
||||||
|
@ -69,7 +68,7 @@ class TestWorkflowService(BaseTest):
|
||||||
task = processor.next_task()
|
task = processor.next_task()
|
||||||
WorkflowService.process_options(task, task.task_spec.form.fields[0])
|
WorkflowService.process_options(task, task.task_spec.form.fields[0])
|
||||||
options = task.task_spec.form.fields[0].options
|
options = task.task_spec.form.fields[0].options
|
||||||
self.assertEquals(19, len(options))
|
self.assertEquals(28, len(options))
|
||||||
self.assertEquals('1000', options[0]['id'])
|
self.assertEquals('1000', options[0]['id'])
|
||||||
self.assertEquals("UVA - INTERNAL - GM USE ONLY", options[0]['name'])
|
self.assertEquals("UVA - INTERNAL - GM USE ONLY", options[0]['name'])
|
||||||
|
|
||||||
|
@ -87,7 +86,7 @@ class TestWorkflowService(BaseTest):
|
||||||
self.assertEquals("CUSTOMER_NAME", lookup_record.label_column)
|
self.assertEquals("CUSTOMER_NAME", lookup_record.label_column)
|
||||||
self.assertEquals("CUSTOMER_NAME", lookup_record.label_column)
|
self.assertEquals("CUSTOMER_NAME", lookup_record.label_column)
|
||||||
lookup_data = session.query(LookupDataModel).filter(LookupDataModel.lookup_file_model == lookup_record).all()
|
lookup_data = session.query(LookupDataModel).filter(LookupDataModel.lookup_file_model == lookup_record).all()
|
||||||
self.assertEquals(19, len(lookup_data))
|
self.assertEquals(28, len(lookup_data))
|
||||||
|
|
||||||
self.assertEquals("1000", lookup_data[0].value)
|
self.assertEquals("1000", lookup_data[0].value)
|
||||||
self.assertEquals("UVA - INTERNAL - GM USE ONLY", lookup_data[0].label)
|
self.assertEquals("UVA - INTERNAL - GM USE ONLY", lookup_data[0].label)
|
||||||
|
@ -103,4 +102,12 @@ class TestWorkflowService(BaseTest):
|
||||||
self.assertEquals(2, len(search_results))
|
self.assertEquals(2, len(search_results))
|
||||||
|
|
||||||
|
|
||||||
|
def test_random_data_populate_form_on_auto_complete(self):
|
||||||
|
self.load_example_data()
|
||||||
|
workflow = self.create_workflow('enum_options_with_search')
|
||||||
|
processor = WorkflowProcessor(workflow)
|
||||||
|
processor.do_engine_steps()
|
||||||
|
task = processor.next_task()
|
||||||
|
task_api = WorkflowService.spiff_task_to_api_task(task, add_docs_and_forms=True)
|
||||||
|
WorkflowService.populate_form_with_random_data(task, task_api)
|
||||||
|
self.assertTrue(isinstance(task.data["sponsor"], dict))
|
|
@ -1,9 +1,9 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from tests.base_test import BaseTest
|
||||||
from crc import session
|
from crc import session
|
||||||
from crc.models.file import FileModel
|
from crc.models.file import FileModel
|
||||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowSpecCategoryModel
|
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowSpecCategoryModel
|
||||||
from tests.base_test import BaseTest
|
|
||||||
|
|
||||||
|
|
||||||
class TestWorkflowSpec(BaseTest):
|
class TestWorkflowSpec(BaseTest):
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import json
|
import json
|
||||||
import unittest
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from crc import session
|
|
||||||
from crc.api.common import ApiErrorSchema
|
|
||||||
from crc.models.file import FileModel
|
|
||||||
from crc.models.protocol_builder import ProtocolBuilderStudySchema
|
|
||||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowSpecCategoryModel
|
|
||||||
from tests.base_test import BaseTest
|
from tests.base_test import BaseTest
|
||||||
|
|
||||||
|
from crc.services.protocol_builder import ProtocolBuilderService
|
||||||
|
from crc import session, app
|
||||||
|
from crc.api.common import ApiErrorSchema
|
||||||
|
from crc.models.protocol_builder import ProtocolBuilderStudySchema
|
||||||
|
from crc.models.workflow import WorkflowSpecModel
|
||||||
|
|
||||||
|
|
||||||
class TestWorkflowSpecValidation(BaseTest):
|
class TestWorkflowSpecValidation(BaseTest):
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class TestWorkflowSpecValidation(BaseTest):
|
||||||
return ApiErrorSchema(many=True).load(json_data)
|
return ApiErrorSchema(many=True).load(json_data)
|
||||||
|
|
||||||
def test_successful_validation_of_test_workflows(self):
|
def test_successful_validation_of_test_workflows(self):
|
||||||
|
app.config['PB_ENABLED'] = False # Assure this is disabled.
|
||||||
self.assertEqual(0, len(self.validate_workflow("parallel_tasks")))
|
self.assertEqual(0, len(self.validate_workflow("parallel_tasks")))
|
||||||
self.assertEqual(0, len(self.validate_workflow("decision_table")))
|
self.assertEqual(0, len(self.validate_workflow("decision_table")))
|
||||||
self.assertEqual(0, len(self.validate_workflow("docx")))
|
self.assertEqual(0, len(self.validate_workflow("docx")))
|
||||||
|
@ -35,7 +35,7 @@ class TestWorkflowSpecValidation(BaseTest):
|
||||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_required_docs') # mock_docs
|
||||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_study_details') # mock_details
|
||||||
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
|
@patch('crc.services.protocol_builder.ProtocolBuilderService.get_studies') # mock_studies
|
||||||
def test_successful_validation_of_auto_loaded_workflows(self, mock_studies, mock_details, mock_docs, mock_investigators):
|
def test_successful_validation_of_crc_workflows(self, mock_studies, mock_details, mock_docs, mock_investigators):
|
||||||
|
|
||||||
# Mock Protocol Builder responses
|
# Mock Protocol Builder responses
|
||||||
studies_response = self.protocol_builder_response('user_studies.json')
|
studies_response = self.protocol_builder_response('user_studies.json')
|
||||||
|
@ -47,7 +47,8 @@ class TestWorkflowSpecValidation(BaseTest):
|
||||||
investigators_response = self.protocol_builder_response('investigators.json')
|
investigators_response = self.protocol_builder_response('investigators.json')
|
||||||
mock_investigators.return_value = json.loads(investigators_response)
|
mock_investigators.return_value = json.loads(investigators_response)
|
||||||
|
|
||||||
self.load_example_data()
|
self.load_example_data(use_crc_data=True)
|
||||||
|
app.config['PB_ENABLED'] = True
|
||||||
workflows = session.query(WorkflowSpecModel).all()
|
workflows = session.query(WorkflowSpecModel).all()
|
||||||
errors = []
|
errors = []
|
||||||
for w in workflows:
|
for w in workflows:
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
from werkzeug.middleware.dispatcher import DispatcherMiddleware
|
||||||
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
|
|
||||||
|
from crc import app
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
def no_app(environ, start_response):
|
||||||
|
return NotFound()(environ, start_response)
|
||||||
|
|
||||||
|
# Remove trailing slash, but add leading slash
|
||||||
|
base_url = '/' + app.config['APPLICATION_ROOT'].strip('/')
|
||||||
|
routes = {'/': app.wsgi_app}
|
||||||
|
|
||||||
|
if base_url != '/':
|
||||||
|
routes[base_url] = app.wsgi_app
|
||||||
|
|
||||||
|
app.wsgi_app = DispatcherMiddleware(no_app, routes)
|
||||||
|
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||||
|
|
||||||
|
flask_port = app.config['FLASK_PORT']
|
||||||
|
|
||||||
|
app.run(host='0.0.0.0', port=flask_port)
|
Loading…
Reference in New Issue