commit
a6858d1cc5
|
@ -47,10 +47,10 @@
|
|||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38",
|
||||
"sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"
|
||||
"sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5",
|
||||
"sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"
|
||||
],
|
||||
"version": "==2.8.0"
|
||||
"version": "==2.9.0"
|
||||
},
|
||||
"bcrypt": {
|
||||
"hashes": [
|
||||
|
@ -80,10 +80,10 @@
|
|||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
|
||||
"sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
|
||||
"sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd",
|
||||
"sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4"
|
||||
],
|
||||
"version": "==2020.6.20"
|
||||
"version": "==2020.11.8"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
|
@ -221,11 +221,11 @@
|
|||
},
|
||||
"docxtpl": {
|
||||
"hashes": [
|
||||
"sha256:5c98ea92ce31bcf24357a03d193d5096f16b9618d2d6b9b3f8fff01a4081eaf0",
|
||||
"sha256:b5f1823de88b1762cfb1fa86fed6e088ab87aa22f7c157b23b2d1bffb38272f9"
|
||||
"sha256:aa12f454f798daa43b52c41f1c90f890f0405b128f1dadd5aeee501d7e347855",
|
||||
"sha256:dd2fc41f682c0d8f9e8479af167b3b8e781cc0a7b343af37a9a5aaff6dd035d7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.11.1"
|
||||
"version": "==0.11.2"
|
||||
},
|
||||
"et-xmlfile": {
|
||||
"hashes": [
|
||||
|
@ -471,11 +471,11 @@
|
|||
},
|
||||
"marshmallow": {
|
||||
"hashes": [
|
||||
"sha256:4bc31ab18133083b12893c61f2fc38b93c390d3fd6ae2ac61980b7dc936a1afa",
|
||||
"sha256:97ad6acaf727be986330969cff16040fce051510759ca709de9cd48093c55d04"
|
||||
"sha256:73facc37462dfc0b27f571bdaffbef7709e19f7a616beb3802ea425b07843f4e",
|
||||
"sha256:e26763201474b588d144dae9a32bdd945cd26a06c943bc746a6882e850475378"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.9.0"
|
||||
"version": "==3.9.1"
|
||||
},
|
||||
"marshmallow-enum": {
|
||||
"hashes": [
|
||||
|
@ -487,11 +487,11 @@
|
|||
},
|
||||
"marshmallow-sqlalchemy": {
|
||||
"hashes": [
|
||||
"sha256:098d4486374d0c63b4a947269c36d8850eedf35c59f172c5a1da28c070af64f4",
|
||||
"sha256:cf0c7fead0b30ad66892a0cefcb39151591842ab16ca48ffa5bd816719b2491e"
|
||||
"sha256:93f47b880ac7070f7b34c8ac0a71eeec3f8582a22e5c0330c1c436e3f5f99a37",
|
||||
"sha256:d051cf013c075c43e1ee5c4b01f8fab6dd6b140dab6825be45875f674a0d289c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.24.0"
|
||||
"version": "==0.24.1"
|
||||
},
|
||||
"numpy": {
|
||||
"hashes": [
|
||||
|
@ -746,11 +746,11 @@
|
|||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
|
||||
"sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
|
||||
"sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8",
|
||||
"sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.24.0"
|
||||
"version": "==2.25.0"
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"extras": [
|
||||
|
@ -787,11 +787,11 @@
|
|||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:1c21e7c5481a31b531e6cbf59c3292852ccde175b504b00ce2ff0b8f4adc3649",
|
||||
"sha256:3abdb2c57a65afaaa4f8573cbabd5465078eb6fd282c1e4f87f006875a7ec0c7"
|
||||
"sha256:1e8d592225447104d1172be415bc2972bd1357e3e12fdc76edf2261105db4300",
|
||||
"sha256:d4e59ad4ea55efbb3c05cde3bfc83bfc14f0c95aa95c3d75346fcce186a47960"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.3.0"
|
||||
"version": "==3.3.1"
|
||||
},
|
||||
"sphinxcontrib-applehelp": {
|
||||
"hashes": [
|
||||
|
@ -837,7 +837,7 @@
|
|||
},
|
||||
"spiffworkflow": {
|
||||
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
||||
"ref": "a1d5c2feedc2a9490110d5a182ee260c76ed6e66"
|
||||
"ref": "8925f8de20e8fcef2b8c639afce6ddec9d2b9efe"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
|
@ -892,10 +892,10 @@
|
|||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
|
||||
"sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
|
||||
"sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08",
|
||||
"sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"
|
||||
],
|
||||
"version": "==1.25.11"
|
||||
"version": "==1.26.2"
|
||||
},
|
||||
"waitress": {
|
||||
"hashes": [
|
||||
|
|
|
@ -26,10 +26,14 @@ DB_PORT = environ.get('DB_PORT', default="5432")
|
|||
DB_NAME = environ.get('DB_NAME', default="crc_dev")
|
||||
DB_USER = environ.get('DB_USER', default="crc_user")
|
||||
DB_PASSWORD = environ.get('DB_PASSWORD', default="crc_pass")
|
||||
SQLALCHEMY_ENGINE_OPTIONS = {"pool_pre_ping": True} # May help with some disconnect issues.
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
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)
|
||||
)
|
||||
|
||||
|
||||
TOKEN_AUTH_TTL_HOURS = float(environ.get('TOKEN_AUTH_TTL_HOURS', default=24))
|
||||
SECRET_KEY = environ.get('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")
|
||||
|
|
|
@ -19,7 +19,6 @@ connexion_app = connexion.FlaskApp(__name__)
|
|||
|
||||
app = connexion_app.app
|
||||
app.config.from_object('config.default')
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
|
||||
if "TESTING" in os.environ and os.environ["TESTING"] == "true":
|
||||
app.config.from_object('config.testing')
|
||||
|
@ -28,6 +27,7 @@ else:
|
|||
app.config.root_path = app.instance_path
|
||||
app.config.from_pyfile('config.py', silent=True)
|
||||
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
""":type: sqlalchemy.orm.SQLAlchemy"""
|
||||
|
||||
|
|
150
crc/api.yml
150
crc/api.yml
|
@ -1026,6 +1026,122 @@ paths:
|
|||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Approval"
|
||||
/datastore:
|
||||
post:
|
||||
operationId: crc.api.data_store.add_datastore
|
||||
summary: Add datastore item with the given parameters.
|
||||
tags:
|
||||
- DataStore
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DataStore'
|
||||
responses:
|
||||
'200':
|
||||
description: Datastore updated successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DataStore"
|
||||
|
||||
/datastore/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The key to lookup.
|
||||
schema:
|
||||
type: string
|
||||
format: string
|
||||
|
||||
get:
|
||||
operationId: crc.api.data_store.datastore_get
|
||||
summary: Get a datastore item by id
|
||||
tags:
|
||||
- DataStore
|
||||
responses:
|
||||
'200':
|
||||
description: A value from the data store, or a default if provided, or None if not found.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DataStore"
|
||||
put:
|
||||
operationId: crc.api.data_store.update_datastore
|
||||
summary: Updates an existing datastore item with the given parameters.
|
||||
tags:
|
||||
- DataStore
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DataStore'
|
||||
responses:
|
||||
'200':
|
||||
description: Datastore updated successfully.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DataStore"
|
||||
|
||||
|
||||
delete:
|
||||
operationId: crc.api.data_store.datastore_del
|
||||
summary: Deletes a value from the data store by id
|
||||
tags:
|
||||
- DataStore
|
||||
responses:
|
||||
'200':
|
||||
description: Deletes a value from a data store.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DataStore"
|
||||
|
||||
|
||||
/datastore/study/{study_id}:
|
||||
parameters:
|
||||
- name: study_id
|
||||
in: path
|
||||
required: true
|
||||
description: The study id we are concerned with .
|
||||
schema:
|
||||
type: integer
|
||||
format: int32
|
||||
get:
|
||||
operationId: crc.api.data_store.study_multi_get
|
||||
summary: Gets all datastore items for a study_id
|
||||
tags:
|
||||
- DataStore
|
||||
responses:
|
||||
'200':
|
||||
description: Get all values from the data store for a study_id
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DataStore"
|
||||
/datastore/user/{user_id}:
|
||||
parameters:
|
||||
- name: user_id
|
||||
in: path
|
||||
required: true
|
||||
description: The user id we are concerned with .
|
||||
schema:
|
||||
type: string
|
||||
format: string
|
||||
get:
|
||||
operationId: crc.api.data_store.user_multi_get
|
||||
summary: Gets all datastore items by user_id
|
||||
tags:
|
||||
- DataStore
|
||||
responses:
|
||||
'200':
|
||||
description: Get all values from the data store for a user_id.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/DataStore"
|
||||
components:
|
||||
securitySchemes:
|
||||
jwt:
|
||||
|
@ -1096,6 +1212,39 @@ components:
|
|||
type: string
|
||||
x-nullable: true
|
||||
example: "27b-6-1212"
|
||||
DataStore:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
example: 1234
|
||||
key:
|
||||
type: string
|
||||
example: MyKey
|
||||
workflow_id:
|
||||
type: integer
|
||||
x-nullable: true
|
||||
example: 12
|
||||
study_id:
|
||||
type: integer
|
||||
x-nullable: true
|
||||
example: 42
|
||||
user_id:
|
||||
type: string
|
||||
x-nullable: true
|
||||
example: dhf8r
|
||||
task_id:
|
||||
type: string
|
||||
x-nullable: true
|
||||
example: MyTask
|
||||
spec_id:
|
||||
type: string
|
||||
x-nullable: true
|
||||
example: My Spec Name
|
||||
value:
|
||||
type: string
|
||||
x-nullable: true
|
||||
example: Some Value
|
||||
|
||||
WorkflowSpec:
|
||||
properties:
|
||||
id:
|
||||
|
@ -1495,4 +1644,3 @@ components:
|
|||
type: number
|
||||
format: integer
|
||||
example: 5
|
||||
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from crc import session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.data_store import DataStoreModel, DataStoreSchema
|
||||
from crc.scripts.data_store_base import DataStoreBase
|
||||
|
||||
|
||||
def study_data_set(study_id, key, value):
|
||||
"""Set a study data value in the data_store, mimic the script endpoint"""
|
||||
if study_id is None:
|
||||
raise ApiError('unknown_study', 'Please provide a valid Study ID.')
|
||||
|
||||
if key is None:
|
||||
raise ApiError('invalid_key', 'Please provide a valid key')
|
||||
dsb = DataStoreBase()
|
||||
retval = dsb.set_data_common('api', study_id, None, None, None, 'api_study_data_set', key, value)
|
||||
json_value = json.dumps(retval, ensure_ascii=False, indent=2)
|
||||
return json_value
|
||||
|
||||
|
||||
def study_data_get(study_id, key, default=None):
|
||||
"""Get a study data value in the data_store, mimic the script endpoint"""
|
||||
if study_id is None:
|
||||
raise ApiError('unknown_study', 'Please provide a valid Study ID.')
|
||||
|
||||
if key is None:
|
||||
raise ApiError('invalid_key', 'Please provide a valid key')
|
||||
dsb = DataStoreBase()
|
||||
retval = dsb.get_data_common(study_id, None, 'api_study_data_get', key, default)
|
||||
# json_value = json.dumps(retval, ensure_ascii=False, indent=2) # just return raw text
|
||||
return retval
|
||||
|
||||
|
||||
def study_multi_get(study_id):
|
||||
"""Get all data_store values for a given study_id study"""
|
||||
if study_id is None:
|
||||
raise ApiError('unknown_study', 'Please provide a valid Study ID.')
|
||||
|
||||
dsb = DataStoreBase()
|
||||
retval = dsb.get_multi_common(study_id, None)
|
||||
results = DataStoreSchema(many=True).dump(retval)
|
||||
return results
|
||||
|
||||
|
||||
def study_data_del(study_id, key):
|
||||
"""Delete a study data value in the data store"""
|
||||
if study_id is None:
|
||||
raise ApiError('unknown_study', 'Please provide a valid Study ID.')
|
||||
|
||||
if key is None:
|
||||
raise ApiError('invalid_key', 'Please provide a valid key')
|
||||
dsb = DataStoreBase()
|
||||
dsb.del_data_common(study_id, None, 'api_study_data_get', key)
|
||||
json_value = json.dumps('deleted', ensure_ascii=False, indent=2)
|
||||
return json_value
|
||||
|
||||
|
||||
def user_data_set(user_id, key, value):
|
||||
"""Set a user data value in the data_store, mimic the script endpoint"""
|
||||
if user_id is None:
|
||||
raise ApiError('unknown_study', 'Please provide a valid UserID.')
|
||||
|
||||
if key is None:
|
||||
raise ApiError('invalid_key', 'Please provide a valid key')
|
||||
dsb = DataStoreBase()
|
||||
|
||||
retval = dsb.set_data_common('api',
|
||||
None,
|
||||
user_id,
|
||||
None,
|
||||
None,
|
||||
'api_user_data_set',
|
||||
key, value)
|
||||
|
||||
json_value = json.dumps(retval, ensure_ascii=False, indent=2)
|
||||
return json_value
|
||||
|
||||
|
||||
def user_data_get(user_id, key, default=None):
|
||||
"""Get a user data value from the data_store, mimic the script endpoint"""
|
||||
if user_id is None:
|
||||
raise ApiError('unknown_study', 'Please provide a valid UserID.')
|
||||
|
||||
if key is None:
|
||||
raise ApiError('invalid_key', 'Please provide a valid key')
|
||||
dsb = DataStoreBase()
|
||||
retval = dsb.get_data_common(None,
|
||||
user_id,
|
||||
'api_user_data_get',
|
||||
key, default)
|
||||
|
||||
# json_value = json.dumps(retval, ensure_ascii=False, indent=2) # just return raw text
|
||||
return retval
|
||||
|
||||
|
||||
def user_multi_get(user_id):
|
||||
"""Get all data values in the data_store for a userid"""
|
||||
if user_id is None:
|
||||
raise ApiError('unknown_study', 'Please provide a valid UserID.')
|
||||
|
||||
dsb = DataStoreBase()
|
||||
retval = dsb.get_multi_common(None,
|
||||
user_id)
|
||||
results = DataStoreSchema(many=True).dump(retval)
|
||||
return results
|
||||
|
||||
|
||||
def datastore_del(id):
|
||||
"""Delete a data store item for a user_id and a key"""
|
||||
session.query(DataStoreModel).filter_by(id=id).delete()
|
||||
session.commit()
|
||||
json_value = json.dumps('deleted', ensure_ascii=False, indent=2)
|
||||
return json_value
|
||||
|
||||
|
||||
def datastore_get(id):
|
||||
"""Delete a data store item for a user_id and a key"""
|
||||
item = session.query(DataStoreModel).filter_by(id=id).first()
|
||||
results = DataStoreSchema(many=False).dump(item)
|
||||
return results
|
||||
|
||||
|
||||
def update_datastore(id, body):
|
||||
"""allow a modification to a datastore item """
|
||||
if id is None:
|
||||
raise ApiError('unknown_id', 'Please provide a valid ID.')
|
||||
|
||||
item = session.query(DataStoreModel).filter_by(id=id).first()
|
||||
if item is None:
|
||||
raise ApiError('unknown_item', 'The item "' + id + '" is not recognized.')
|
||||
print(body)
|
||||
# I'm not sure if there is a generic way to use the
|
||||
# schema to both parse the body and update the SQLAlchemy record
|
||||
for key in body:
|
||||
if hasattr(item, key):
|
||||
setattr(item, key, body[key])
|
||||
item.last_updated = datetime.now()
|
||||
session.add(item)
|
||||
session.commit()
|
||||
return DataStoreSchema().dump(item)
|
||||
|
||||
|
||||
def add_datastore(body):
|
||||
""" add a new datastore item """
|
||||
|
||||
print(body)
|
||||
if body.get(id, None):
|
||||
raise ApiError('id_specified', 'You may not specify an id for a new datastore item')
|
||||
|
||||
if 'key' not in body:
|
||||
raise ApiError('no_key', 'You need to specify a key to add a datastore item')
|
||||
|
||||
if 'value' not in body:
|
||||
raise ApiError('no_value', 'You need to specify a value to add a datastore item')
|
||||
|
||||
if (not 'user_id' in body) and (not 'study_id' in body):
|
||||
raise ApiError('conflicting_values', 'A datastore item should have either a study_id or a user_id')
|
||||
|
||||
if 'user_id' in body and 'study_id' in body:
|
||||
raise ApiError('conflicting_values', 'A datastore item should have either a study_id or a user_id, '
|
||||
'but not both')
|
||||
|
||||
item = DataStoreModel(key=body['key'], value=body['value'])
|
||||
# I'm not sure if there is a generic way to use the
|
||||
# schema to both parse the body and update the SQLAlchemy record
|
||||
for key in body:
|
||||
if hasattr(item, key):
|
||||
setattr(item, key, body[key])
|
||||
item.last_updated = datetime.now()
|
||||
session.add(item)
|
||||
session.commit()
|
||||
return DataStoreSchema().dump(item)
|
|
@ -147,6 +147,9 @@ def set_current_task(workflow_id, task_id):
|
|||
raise ApiError("invalid_state", "You may not move the token to a task who's state is not "
|
||||
"currently set to COMPLETE or READY.")
|
||||
|
||||
# If we have an interrupt task, run it.
|
||||
processor.bpmn_workflow.cancel_notify()
|
||||
|
||||
# Only reset the token if the task doesn't already have it.
|
||||
if spiff_task.state == spiff_task.COMPLETED:
|
||||
spiff_task.reset_token(reset_data=True) # Don't try to copy the existing data back into this task.
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
from flask_marshmallow.sqla import SQLAlchemyAutoSchema
|
||||
from marshmallow import EXCLUDE
|
||||
from sqlalchemy import func
|
||||
import marshmallow
|
||||
from marshmallow import INCLUDE, fields
|
||||
|
||||
from crc import db, ma
|
||||
|
||||
class DataStoreModel(db.Model):
|
||||
__tablename__ = 'data_store'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
last_updated = db.Column(db.DateTime(timezone=True), default=func.now())
|
||||
key = db.Column(db.String, nullable=False)
|
||||
workflow_id = db.Column(db.Integer)
|
||||
study_id = db.Column(db.Integer, nullable=True)
|
||||
task_id = db.Column(db.String)
|
||||
spec_id = db.Column(db.String)
|
||||
user_id = db.Column(db.String, nullable=True)
|
||||
value = db.Column(db.String)
|
||||
|
||||
|
||||
class DataStoreSchema(ma.Schema):
|
||||
id = fields.Integer(required=False)
|
||||
key = fields.String(required=True)
|
||||
last_updated = fields.DateTime(server_default=func.now(), onupdate=func.now())
|
||||
workflow_id = fields.Integer()
|
||||
study_id = fields.Integer(allow_none=True)
|
||||
task_id = fields.String()
|
||||
spec_id = fields.String()
|
||||
user_id = fields.String(allow_none=True)
|
||||
value = fields.String()
|
|
@ -0,0 +1,80 @@
|
|||
import importlib
|
||||
import os
|
||||
import pkgutil
|
||||
from crc import session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.data_store import DataStoreModel
|
||||
from crc.models.workflow import WorkflowModel
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DataStoreBase(object):
|
||||
|
||||
def overwritten(self, value, prev_value):
|
||||
if prev_value is None:
|
||||
overwritten = False
|
||||
else:
|
||||
if prev_value == value:
|
||||
overwritten = False
|
||||
else:
|
||||
overwritten = True
|
||||
return overwritten
|
||||
|
||||
def set_validate_common(self, study_id, workflow_id, user_id, script_name, *args):
|
||||
self.check_args_2(args, script_name)
|
||||
workflow = session.query(WorkflowModel).filter(WorkflowModel.id == workflow_id).first()
|
||||
self.get_prev_value(study_id=study_id, user_id=user_id, key=args[0])
|
||||
|
||||
def check_args(self, args, maxlen=1, script_name='study_data_get'):
|
||||
if len(args) < 1 or len(args) > maxlen:
|
||||
raise ApiError(code="missing_argument",
|
||||
message=f"The {script_name} script takes either one or two arguments, "
|
||||
f"starting with the key and an optional default")
|
||||
|
||||
def check_args_2(self, args, script_name='study_data_set'):
|
||||
if len(args) != 2:
|
||||
raise ApiError(code="missing_argument",
|
||||
message=f"The {script_name} script takes two arguments, starting with the key and a "
|
||||
"value for the key")
|
||||
|
||||
def get_prev_value(self, study_id, user_id, key):
|
||||
study = session.query(DataStoreModel).filter_by(study_id=study_id, user_id=user_id, key=key).first()
|
||||
return study
|
||||
|
||||
def set_data_common(self, task_id, study_id, user_id, workflow_id, workflow_spec_id, script_name, *args, **kwargs):
|
||||
|
||||
self.check_args_2(args, script_name=script_name)
|
||||
study = self.get_prev_value(study_id=study_id, user_id=user_id, key=args[0])
|
||||
if workflow_spec_id is None and workflow_id is not None:
|
||||
workflow = session.query(WorkflowModel).filter(WorkflowModel.id == workflow_id).first()
|
||||
workflow_spec_id = workflow.workflow_spec_id
|
||||
if study is not None:
|
||||
prev_value = study.value
|
||||
else:
|
||||
prev_value = None
|
||||
study = DataStoreModel(key=args[0], value=args[1],
|
||||
study_id=study_id,
|
||||
task_id=task_id,
|
||||
user_id=user_id, # Make this available to any User
|
||||
workflow_id=workflow_id,
|
||||
spec_id=workflow_spec_id)
|
||||
study.value = args[1]
|
||||
study.last_updated = datetime.now()
|
||||
overwritten = self.overwritten(study.value, prev_value)
|
||||
session.add(study)
|
||||
session.commit()
|
||||
return {'new_value': study.value,
|
||||
'old_value': prev_value,
|
||||
'overwritten': overwritten}
|
||||
|
||||
def get_data_common(self, study_id, user_id, script_name, *args):
|
||||
self.check_args(args, 2, script_name)
|
||||
study = session.query(DataStoreModel).filter_by(study_id=study_id, user_id=user_id, key=args[0]).first()
|
||||
if study:
|
||||
return study.value
|
||||
else:
|
||||
return args[1]
|
||||
|
||||
def get_multi_common(self, study_id, user_id):
|
||||
study = session.query(DataStoreModel).filter_by(study_id=study_id, user_id=user_id)
|
||||
return study
|
|
@ -68,9 +68,6 @@ class Script(object):
|
|||
workflow_id)
|
||||
return execlist
|
||||
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_all_subclasses():
|
||||
return Script._get_all_subclasses(Script)
|
||||
|
@ -109,3 +106,6 @@ class ScriptValidationError:
|
|||
@classmethod
|
||||
def from_api_error(cls, api_error: ApiError):
|
||||
return cls(api_error.code, api_error.message)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
from crc.scripts.data_store_base import DataStoreBase
|
||||
from crc.scripts.script import Script
|
||||
|
||||
|
||||
class StudyDataGet(Script,DataStoreBase):
|
||||
def get_description(self):
|
||||
return """Gets study data from the data store."""
|
||||
|
||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
self.do_task(task, study_id, workflow_id, *args, **kwargs)
|
||||
|
||||
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
return self.get_data_common(study_id,
|
||||
None,
|
||||
'study_data_get',
|
||||
*args)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
from crc.scripts.data_store_base import DataStoreBase
|
||||
from crc.scripts.script import Script
|
||||
|
||||
|
||||
class StudyDataSet(Script,DataStoreBase):
|
||||
def get_description(self):
|
||||
return """Sets study data from the data store."""
|
||||
|
||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
self.set_validate_common(study_id,
|
||||
workflow_id,
|
||||
None,
|
||||
'study_data_set',
|
||||
*args)
|
||||
|
||||
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
return self.set_data_common(task.id,
|
||||
study_id,
|
||||
None,
|
||||
workflow_id,
|
||||
None,
|
||||
'study_data_set',
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
from flask import g
|
||||
|
||||
from crc.scripts.data_store_base import DataStoreBase
|
||||
from crc.scripts.script import Script
|
||||
|
||||
|
||||
class UserDataGet(Script, DataStoreBase):
|
||||
def get_description(self):
|
||||
return """Gets user data from the data store."""
|
||||
|
||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
self.do_task(task, study_id, workflow_id, *args, **kwargs)
|
||||
|
||||
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
return self.get_data_common(None,
|
||||
g.user.uid,
|
||||
'user_data_get',
|
||||
*args)
|
|
@ -0,0 +1,29 @@
|
|||
from flask import g
|
||||
|
||||
from crc.scripts.data_store_base import DataStoreBase
|
||||
from crc.scripts.script import Script
|
||||
|
||||
|
||||
class UserDataSet(Script,DataStoreBase):
|
||||
def get_description(self):
|
||||
return """Sets user data to the data store."""
|
||||
|
||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
self.set_validate_common(None,
|
||||
workflow_id,
|
||||
g.user.uid,
|
||||
'user_data_set',
|
||||
*args)
|
||||
|
||||
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
return self.set_data_common(task.id,
|
||||
None,
|
||||
g.user.uid,
|
||||
workflow_id,
|
||||
None,
|
||||
'user_data_set',
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 0718ad13e5f3
|
||||
Revises: 69081f1ff387
|
||||
Create Date: 2020-11-06 11:08:33.657440
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0718ad13e5f3'
|
||||
down_revision = '69081f1ff387'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('data_store',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('key', sa.String(), nullable=False),
|
||||
sa.Column('value', sa.String(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('data_store')
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,36 @@
|
|||
"""add columns
|
||||
|
||||
Revision ID: e0dfdbfd6f69
|
||||
Revises: 0718ad13e5f3
|
||||
Create Date: 2020-11-09 08:33:04.585139
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'e0dfdbfd6f69'
|
||||
down_revision = '0718ad13e5f3'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('data_store', sa.Column('spec_id', sa.String(), nullable=True))
|
||||
op.add_column('data_store', sa.Column('study_id', sa.Integer(), nullable=True))
|
||||
op.add_column('data_store', sa.Column('task_id', sa.String(), nullable=True))
|
||||
op.add_column('data_store', sa.Column('user_id', sa.String(), nullable=True))
|
||||
op.add_column('data_store', sa.Column('workflow_id', sa.Integer(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('data_store', 'workflow_id')
|
||||
op.drop_column('data_store', 'user_id')
|
||||
op.drop_column('data_store', 'task_id')
|
||||
op.drop_column('data_store', 'study_id')
|
||||
op.drop_column('data_store', 'spec_id')
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,28 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: f186725c1ad3
|
||||
Revises: e0dfdbfd6f69
|
||||
Create Date: 2020-11-13 11:01:31.882424
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f186725c1ad3'
|
||||
down_revision = 'e0dfdbfd6f69'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('data_store', sa.Column('last_updated', sa.DateTime(timezone=True), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('data_store', 'last_updated')
|
||||
# ### end Alembic commands ###
|
|
@ -17,6 +17,7 @@ from crc.models.approval import ApprovalModel, ApprovalStatus
|
|||
from crc.models.file import FileModel, FileDataModel, CONTENT_TYPES
|
||||
from crc.models.task_event import TaskEventModel
|
||||
from crc.models.study import StudyModel, StudyStatus
|
||||
from crc.models.data_store import DataStoreModel
|
||||
from crc.models.user import UserModel
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecCategoryModel
|
||||
from crc.services.file_service import FileService
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
<?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_0a9entn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||
<bpmn:process id="Process_1dagb7t" name="TestMessage" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1" name="Start">
|
||||
<bpmn:outgoing>Flow_0xym55y</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_16q1uec" name="TestMessageFlow" sourceRef="Event_TokenReset" targetRef="Activity_TestMessage" />
|
||||
<bpmn:scriptTask id="Activity_TestMessage" name="Test Message" camunda:resultVariable="test_message">
|
||||
<bpmn:incoming>Flow_16q1uec</bpmn:incoming>
|
||||
<bpmn:script>update_study("title:'New Title'")
|
||||
print('New Title')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:userTask id="Activity_GetData" name="GetData" camunda:formKey="FirstTaskForm">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="formdata" label="Gimme some data" type="string" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1rvh899</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1n1fs6z</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:scriptTask id="Activity_PrintData" name="PrintData">
|
||||
<bpmn:incoming>Flow_07i0gvv</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1c2tudh</bpmn:outgoing>
|
||||
<bpmn:script>[print(formdata) for _ in range(how_many)]
|
||||
printdata = formdata</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="Event_1b8jy9y" name="EndEvent">
|
||||
<bpmn:incoming>Flow_1c2tudh</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_1c2tudh" sourceRef="Activity_PrintData" targetRef="Event_1b8jy9y" />
|
||||
<bpmn:sequenceFlow id="Flow_0xym55y" sourceRef="StartEvent_1" targetRef="Activity_Hello" />
|
||||
<bpmn:sequenceFlow id="Flow_1rvh899" sourceRef="Activity_Hello" targetRef="Activity_GetData" />
|
||||
<bpmn:scriptTask id="Activity_Hello" name="Hello">
|
||||
<bpmn:incoming>Flow_0xym55y</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1rvh899</bpmn:outgoing>
|
||||
<bpmn:script>print('Hello'); printdata=''; test_message=''</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_1n1fs6z" sourceRef="Activity_GetData" targetRef="Activity_HowMany" />
|
||||
<bpmn:userTask id="Activity_HowMany" name="HowMany" camunda:formKey="HowMany">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="how_many" label="How many?" type="long" defaultValue="1" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_1n1fs6z</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_07i0gvv</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="Flow_07i0gvv" sourceRef="Activity_HowMany" targetRef="Activity_PrintData" />
|
||||
<bpmn:boundaryEvent id="Event_TokenReset" name="TokenReset" attachedToRef="Activity_HowMany">
|
||||
<bpmn:outgoing>Flow_16q1uec</bpmn:outgoing>
|
||||
<bpmn:cancelEventDefinition id="CancelEventDefinition_1d5hszc" />
|
||||
</bpmn:boundaryEvent>
|
||||
</bpmn:process>
|
||||
<bpmn:message id="Message_0iyvlbz" name="token_reset" />
|
||||
<bpmn:message id="Message_1ow6ruy" name="Message_00ldv4i" />
|
||||
<bpmn:signal id="Signal_1fbgshz" name="token_reset" />
|
||||
<bpmn:message id="Message_1czi5ye" name="token_reset" />
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1dagb7t">
|
||||
<bpmndi:BPMNEdge id="Flow_07i0gvv_di" bpmnElement="Flow_07i0gvv">
|
||||
<di:waypoint x="650" y="117" />
|
||||
<di:waypoint x="720" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1n1fs6z_di" bpmnElement="Flow_1n1fs6z">
|
||||
<di:waypoint x="490" y="117" />
|
||||
<di:waypoint x="550" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1rvh899_di" bpmnElement="Flow_1rvh899">
|
||||
<di:waypoint x="330" y="117" />
|
||||
<di:waypoint x="390" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0xym55y_di" bpmnElement="Flow_0xym55y">
|
||||
<di:waypoint x="188" y="117" />
|
||||
<di:waypoint x="230" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1c2tudh_di" bpmnElement="Flow_1c2tudh">
|
||||
<di:waypoint x="820" y="117" />
|
||||
<di:waypoint x="862" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_16q1uec_di" bpmnElement="Flow_16q1uec">
|
||||
<di:waypoint x="600" y="175" />
|
||||
<di:waypoint x="600" y="269" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="605" y="222" width="89" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="152" y="99" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="158" y="142" width="24" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0bieozg_di" bpmnElement="Activity_TestMessage">
|
||||
<dc:Bounds x="550" y="269" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_04w71at_di" bpmnElement="Activity_GetData">
|
||||
<dc:Bounds x="390" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1xlnpzi_di" bpmnElement="Activity_PrintData">
|
||||
<dc:Bounds x="720" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1b8jy9y_di" bpmnElement="Event_1b8jy9y">
|
||||
<dc:Bounds x="862" y="99" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="857" y="142" width="48" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1jj0s1n_di" bpmnElement="Activity_Hello">
|
||||
<dc:Bounds x="230" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0hkt70o_di" bpmnElement="Activity_HowMany">
|
||||
<dc:Bounds x="550" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1yxxtrb_di" bpmnElement="Event_TokenReset">
|
||||
<dc:Bounds x="582" y="139" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="610" y="173" width="59" height="14" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -0,0 +1,91 @@
|
|||
<?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_0kmksnn" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.0">
|
||||
<bpmn:process id="Process_0exnnpv" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_1nfe5m9</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1nfe5m9" sourceRef="StartEvent_1" targetRef="Task_Script_Load_Study_Sponsors" />
|
||||
<bpmn:scriptTask id="Task_Script_Load_Study_Sponsors" name="Load Study Sponsors">
|
||||
<bpmn:incoming>SequenceFlow_1nfe5m9</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1bqiin0</bpmn:outgoing>
|
||||
<bpmn:script>sponsors = study_info('sponsors')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1bqiin0" sourceRef="Task_Script_Load_Study_Sponsors" targetRef="Activity_0cm6tn2" />
|
||||
<bpmn:endEvent id="EndEvent_171dj09">
|
||||
<bpmn:incoming>Flow_05136ua</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_09cika8" sourceRef="Activity_0cm6tn2" targetRef="Activity_0d8iftx" />
|
||||
<bpmn:scriptTask id="Activity_0cm6tn2" name="setval">
|
||||
<bpmn:incoming>SequenceFlow_1bqiin0</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_09cika8</bpmn:outgoing>
|
||||
<bpmn:script>study_data_set('testme','newval')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_1oeqjuy" sourceRef="Activity_0d8iftx" targetRef="Activity_1yup9u7" />
|
||||
<bpmn:scriptTask id="Activity_0d8iftx" name="getval">
|
||||
<bpmn:incoming>Flow_09cika8</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1oeqjuy</bpmn:outgoing>
|
||||
<bpmn:script>out = study_data_get('testme','bogus')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_0g9waf3" sourceRef="Activity_1yup9u7" targetRef="Activity_0xw717o" />
|
||||
<bpmn:scriptTask id="Activity_1yup9u7" name="reset value">
|
||||
<bpmn:incoming>Flow_1oeqjuy</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0g9waf3</bpmn:outgoing>
|
||||
<bpmn:script>study_data_set('testme','badval')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:scriptTask id="Activity_0xw717o" name="Make sure user_data_get doesn't get the study_data_set variable">
|
||||
<bpmn:incoming>Flow_0g9waf3</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_05136ua</bpmn:outgoing>
|
||||
<bpmn:script>empty = user_data_get('testme','empty')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_05136ua" sourceRef="Activity_0xw717o" targetRef="EndEvent_171dj09" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0exnnpv">
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1bqiin0_di" bpmnElement="SequenceFlow_1bqiin0">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="440" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1nfe5m9_di" bpmnElement="SequenceFlow_1nfe5m9">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="ScriptTask_1mp6xid_di" bpmnElement="Task_Script_Load_Study_Sponsors">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_171dj09_di" bpmnElement="EndEvent_171dj09">
|
||||
<dc:Bounds x="792" y="672" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_09cika8_di" bpmnElement="Flow_09cika8">
|
||||
<di:waypoint x="540" y="117" />
|
||||
<di:waypoint x="600" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Activity_0wnwluq_di" bpmnElement="Activity_0cm6tn2">
|
||||
<dc:Bounds x="440" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_1oeqjuy_di" bpmnElement="Flow_1oeqjuy">
|
||||
<di:waypoint x="700" y="117" />
|
||||
<di:waypoint x="760" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Activity_0cq37mm_di" bpmnElement="Activity_0d8iftx">
|
||||
<dc:Bounds x="600" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_0g9waf3_di" bpmnElement="Flow_0g9waf3">
|
||||
<di:waypoint x="810" y="157" />
|
||||
<di:waypoint x="810" y="250" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Activity_0cj83fx_di" bpmnElement="Activity_1yup9u7">
|
||||
<dc:Bounds x="760" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0pqth07_di" bpmnElement="Activity_0xw717o">
|
||||
<dc:Bounds x="760" y="250" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_05136ua_di" bpmnElement="Flow_05136ua">
|
||||
<di:waypoint x="810" y="330" />
|
||||
<di:waypoint x="810" y="672" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -1,12 +1,6 @@
|
|||
from tests.base_test import BaseTest
|
||||
|
||||
from crc import mail
|
||||
from crc.models.email import EmailModel
|
||||
from crc.services.file_service import FileService
|
||||
from crc.scripts.email import Email
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from crc.api.common import ApiError
|
||||
|
||||
from crc import db, mail
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
class TestEmailScript(BaseTest):
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
from unittest.mock import patch
|
||||
import flask
|
||||
|
||||
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
from crc import session, app
|
||||
from crc.models.study import StudyModel
|
||||
from crc.models.user import UserModel
|
||||
from crc.services.study_service import StudyService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from crc.services.workflow_service import WorkflowService
|
||||
|
||||
class TestSudySponsorsScript(BaseTest):
|
||||
test_uid = "dhf8r"
|
||||
test_study_id = 1
|
||||
|
||||
|
||||
def test_study_sponsors_script_validation(self):
|
||||
flask.g.user = UserModel(uid='dhf8r')
|
||||
self.load_example_data() # study_info script complains if irb_documents.xls is not loaded
|
||||
# during the validate phase I'm going to assume that we will never
|
||||
# have a case where irb_documents.xls is not loaded ??
|
||||
|
||||
self.load_test_spec("study_sponsors_data_store")
|
||||
WorkflowService.test_spec("study_sponsors_data_store") # This would raise errors if it didn't validate
|
||||
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_study_sponsors_script(self, mock_get):
|
||||
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('sponsors.json')
|
||||
flask.g.user = UserModel(uid='dhf8r')
|
||||
app.config['PB_ENABLED'] = True
|
||||
|
||||
self.load_example_data()
|
||||
self.create_reference_document()
|
||||
study = session.query(StudyModel).first()
|
||||
workflow_spec_model = self.load_test_spec("study_sponsors_data_store")
|
||||
workflow_model = StudyService._create_workflow_model(study, workflow_spec_model)
|
||||
WorkflowService.test_spec("study_sponsors_data_store")
|
||||
processor = WorkflowProcessor(workflow_model)
|
||||
processor.do_engine_steps()
|
||||
self.assertTrue(processor.bpmn_workflow.is_completed())
|
||||
data = processor.next_task().data
|
||||
self.assertIn('sponsors', data)
|
||||
self.assertIn('out', data)
|
||||
self.assertEquals('empty', data['empty'])
|
||||
self.assertEquals('newval', data['out'])
|
||||
self.assertEquals(3, len(data['sponsors']))
|
|
@ -0,0 +1,120 @@
|
|||
import json
|
||||
from profile import Profile
|
||||
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from unittest.mock import patch
|
||||
from crc.models.data_store import DataStoreModel, DataStoreSchema
|
||||
from crc import session, app
|
||||
|
||||
|
||||
|
||||
|
||||
class DataStoreTest(BaseTest):
|
||||
TEST_STUDY_ITEM = {
|
||||
"key": "MyKey",
|
||||
"workflow_id": 12,
|
||||
"study_id": 42,
|
||||
"task_id": "MyTask",
|
||||
"spec_id": "My Spec Name",
|
||||
"value": "Some Value"
|
||||
|
||||
}
|
||||
def add_test_study_data(self):
|
||||
study_data = DataStoreSchema().dump(self.TEST_STUDY_ITEM)
|
||||
rv = self.app.post('/v1.0/datastore',
|
||||
content_type="application/json",
|
||||
headers=self.logged_in_headers(),
|
||||
data=json.dumps(study_data))
|
||||
self.assert_success(rv)
|
||||
return json.loads(rv.get_data(as_text=True))
|
||||
|
||||
def add_test_user_data(self):
|
||||
study_data = DataStoreSchema().dump(self.TEST_STUDY_ITEM)
|
||||
study_data['user_id'] = 'dhf8r'
|
||||
del(study_data['study_id'])
|
||||
study_data['value'] = 'User Value'
|
||||
rv = self.app.post('/v1.0/datastore',
|
||||
content_type="application/json",
|
||||
headers=self.logged_in_headers(),
|
||||
data=json.dumps(study_data))
|
||||
self.assert_success(rv)
|
||||
return json.loads(rv.get_data(as_text=True))
|
||||
|
||||
|
||||
|
||||
def test_get_study_data(self):
|
||||
"""Generic test, but pretty detailed, in that the study should return a categorized list of workflows
|
||||
This starts with out loading the example data, to show that all the bases are covered from ground 0."""
|
||||
|
||||
"""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."""
|
||||
self.load_example_data()
|
||||
new_study = self.add_test_study_data()
|
||||
new_study = session.query(DataStoreModel).filter_by(id=new_study["id"]).first()
|
||||
|
||||
api_response = self.app.get('/v1.0/datastore/%i' % new_study.id,
|
||||
headers=self.logged_in_headers(), content_type="application/json")
|
||||
self.assert_success(api_response)
|
||||
d = api_response.get_data(as_text=True)
|
||||
study_data = DataStoreSchema().loads(d)
|
||||
|
||||
self.assertEqual(study_data['key'], self.TEST_STUDY_ITEM['key'])
|
||||
self.assertEqual(study_data['value'], self.TEST_STUDY_ITEM['value'])
|
||||
self.assertEqual(study_data['user_id'], None)
|
||||
|
||||
|
||||
|
||||
def test_update_study(self):
|
||||
self.load_example_data()
|
||||
new_study = self.add_test_study_data()
|
||||
new_study = session.query(DataStoreModel).filter_by(id=new_study["id"]).first()
|
||||
new_study.value = 'MyNewValue'
|
||||
api_response = self.app.put('/v1.0/datastore/%i' % new_study.id,
|
||||
data=DataStoreSchema().dump(new_study),
|
||||
headers=self.logged_in_headers(), content_type="application/json")
|
||||
|
||||
|
||||
api_response = self.app.get('/v1.0/datastore/%i' % new_study.id,
|
||||
headers=self.logged_in_headers(), content_type="application/json")
|
||||
self.assert_success(api_response)
|
||||
study_data = DataStoreSchema().loads(api_response.get_data(as_text=True))
|
||||
|
||||
self.assertEqual(study_data['key'], self.TEST_STUDY_ITEM['key'])
|
||||
self.assertEqual(study_data['value'], 'MyNewValue')
|
||||
self.assertEqual(study_data['user_id'], None)
|
||||
|
||||
|
||||
|
||||
def test_delete_study(self):
|
||||
self.load_example_data()
|
||||
new_study = self.add_test_study_data()
|
||||
oldid = new_study['id']
|
||||
new_study = session.query(DataStoreModel).filter_by(id=new_study["id"]).first()
|
||||
rv = self.app.delete('/v1.0/datastore/%i' % new_study.id, headers=self.logged_in_headers())
|
||||
self.assert_success(rv)
|
||||
studyreponse = session.query(DataStoreModel).filter_by(id=oldid).first()
|
||||
self.assertEqual(studyreponse,None)
|
||||
|
||||
|
||||
|
||||
def test_data_crosstalk(self):
|
||||
"""Test to make sure that data saved for user or study is not acessible from the other method"""
|
||||
|
||||
self.load_example_data()
|
||||
new_study = self.add_test_study_data()
|
||||
new_user = self.add_test_user_data()
|
||||
|
||||
api_response = self.app.get(f'/v1.0/datastore/user/{new_user["user_id"]}',
|
||||
headers=self.logged_in_headers(), content_type="application/json")
|
||||
self.assert_success(api_response)
|
||||
d = json.loads(api_response.get_data(as_text=True))
|
||||
self.assertEqual(d[0]['value'],'User Value')
|
||||
|
||||
api_response = self.app.get(f'/v1.0/datastore/study/{new_study["study_id"]}',
|
||||
headers=self.logged_in_headers(), content_type="application/json")
|
||||
|
||||
self.assert_success(api_response)
|
||||
d = json.loads(api_response.get_data(as_text=True))
|
||||
self.assertEqual(d[0]['value'],'Some Value')
|
|
@ -0,0 +1,34 @@
|
|||
from tests.base_test import BaseTest
|
||||
from crc.models.study import StudyModel
|
||||
from crc import db
|
||||
|
||||
|
||||
class TestMessageEvent(BaseTest):
|
||||
|
||||
def test_message_event(self):
|
||||
|
||||
workflow = self.create_workflow('message_event')
|
||||
study_id = workflow.study_id
|
||||
|
||||
# Start the workflow.
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
self.assertEqual('Activity_GetData', first_task.name)
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.complete_form(workflow, first_task, {'formdata': 'asdf'})
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Activity_HowMany', workflow.next_task.name)
|
||||
|
||||
# reset the workflow
|
||||
# this ultimately calls crc.api.workflow.set_current_task
|
||||
self.app.put('/v1.0/workflow/%i/task/%s/set_token' % (
|
||||
workflow.id,
|
||||
first_task.id),
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json")
|
||||
|
||||
# set_current_task should call the interupt (signal) task
|
||||
# which should run the script in our task
|
||||
#
|
||||
# test to see if our changes made it to the DB
|
||||
study_result = db.session.query(StudyModel).filter(StudyModel.id == study_id).first()
|
||||
self.assertEqual('New Title', study_result.title)
|
Loading…
Reference in New Issue