mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-20 19:58:17 +00:00
Merge branch 'dev' into feature/performance-refactor
This commit is contained in:
commit
34759a2f3d
34
crc/api.yml
34
crc/api.yml
@ -433,7 +433,7 @@ paths:
|
||||
- name: spec_id
|
||||
in: path
|
||||
required: true
|
||||
description: The unique id of an existing workflow specification to modify.
|
||||
description: The unique id of an existing workflow specification.
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
@ -448,6 +448,18 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WorkflowSpec"
|
||||
post:
|
||||
operationId: crc.api.workflow.get_workflow_from_spec
|
||||
summary: Creates a workflow from a workflow spec and returns the workflow
|
||||
tags:
|
||||
- Workflow Specifications
|
||||
responses:
|
||||
'200':
|
||||
description: Workflow generated successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
put:
|
||||
operationId: crc.api.workflow.update_workflow_specification
|
||||
security:
|
||||
@ -477,6 +489,21 @@ paths:
|
||||
responses:
|
||||
'204':
|
||||
description: The workflow specification has been removed.
|
||||
/workflow-specification/standalone:
|
||||
get:
|
||||
operationId: crc.api.workflow.standalone_workflow_specs
|
||||
summary: Provides a list of workflow specifications that can be run outside a study.
|
||||
tags:
|
||||
- Workflow Specifications
|
||||
responses:
|
||||
'200':
|
||||
description: A list of workflow specifications
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/WorkflowSpec"
|
||||
/workflow-specification/{spec_id}/validate:
|
||||
parameters:
|
||||
- name: spec_id
|
||||
@ -1544,6 +1571,9 @@ components:
|
||||
category_id:
|
||||
type: integer
|
||||
nullable: true
|
||||
standalone:
|
||||
type: boolean
|
||||
example: false
|
||||
workflow_spec_category:
|
||||
$ref: "#/components/schemas/WorkflowSpecCategory"
|
||||
is_status:
|
||||
@ -1616,6 +1646,8 @@ components:
|
||||
type: integer
|
||||
num_tasks_incomplete:
|
||||
type: integer
|
||||
study_id:
|
||||
type: integer
|
||||
|
||||
example:
|
||||
id: 291234
|
||||
|
@ -55,7 +55,7 @@ def update_datastore(id, body):
|
||||
raise ApiError('unknown_item', 'The item "' + id + '" is not recognized.')
|
||||
|
||||
DataStoreSchema().load(body, instance=item, session=session)
|
||||
item.last_updated = datetime.now()
|
||||
item.last_updated = datetime.utcnow()
|
||||
session.add(item)
|
||||
session.commit()
|
||||
return DataStoreSchema().dump(item)
|
||||
@ -87,7 +87,7 @@ def add_datastore(body):
|
||||
'but not more than one of these')
|
||||
|
||||
item = DataStoreSchema().load(body)
|
||||
item.last_updated = datetime.now()
|
||||
item.last_updated = datetime.utcnow()
|
||||
session.add(item)
|
||||
session.commit()
|
||||
return DataStoreSchema().dump(item)
|
||||
|
@ -23,7 +23,7 @@ def add_study(body):
|
||||
study_model = StudyModel(user_uid=UserService.current_user().uid,
|
||||
title=body['title'],
|
||||
primary_investigator_id=body['primary_investigator_id'],
|
||||
last_updated=datetime.now(),
|
||||
last_updated=datetime.utcnow(),
|
||||
status=StudyStatus.in_progress)
|
||||
session.add(study_model)
|
||||
StudyService.add_study_update_event(study_model,
|
||||
@ -51,7 +51,7 @@ def update_study(study_id, body):
|
||||
study: Study = StudyForUpdateSchema().load(body)
|
||||
|
||||
status = StudyStatus(study.status)
|
||||
study_model.last_updated = datetime.now()
|
||||
study_model.last_updated = datetime.utcnow()
|
||||
|
||||
if study_model.status != status:
|
||||
study_model.status = status
|
||||
|
@ -101,6 +101,24 @@ def delete_workflow_specification(spec_id):
|
||||
session.commit()
|
||||
|
||||
|
||||
def get_workflow_from_spec(spec_id):
|
||||
workflow_model = WorkflowService.get_workflow_from_spec(spec_id, g.user)
|
||||
processor = WorkflowProcessor(workflow_model)
|
||||
|
||||
processor.do_engine_steps()
|
||||
processor.save()
|
||||
WorkflowService.update_task_assignments(processor)
|
||||
|
||||
workflow_api_model = WorkflowService.processor_to_workflow_api(processor)
|
||||
return WorkflowApiSchema().dump(workflow_api_model)
|
||||
|
||||
|
||||
def standalone_workflow_specs():
|
||||
schema = WorkflowSpecModelSchema(many=True)
|
||||
specs = WorkflowService.get_standalone_workflow_specs()
|
||||
return schema.dump(specs)
|
||||
|
||||
|
||||
def get_workflow(workflow_id, do_engine_steps=True):
|
||||
"""Retrieve workflow based on workflow_id, and return it in the last saved State.
|
||||
If do_engine_steps is False, return the workflow without running any engine tasks or logging any events. """
|
||||
@ -185,9 +203,6 @@ def update_task(workflow_id, task_id, body, terminate_loop=None, update_all=Fals
|
||||
if workflow_model is None:
|
||||
raise ApiError("invalid_workflow_id", "The given workflow id is not valid.", status_code=404)
|
||||
|
||||
elif workflow_model.study is None:
|
||||
raise ApiError("invalid_study", "There is no study associated with the given workflow.", status_code=404)
|
||||
|
||||
processor = WorkflowProcessor(workflow_model)
|
||||
task_id = uuid.UUID(task_id)
|
||||
spiff_task = processor.bpmn_workflow.get_task(task_id)
|
||||
|
@ -191,7 +191,7 @@ class DocumentDirectory(object):
|
||||
class WorkflowApi(object):
|
||||
def __init__(self, id, status, next_task, navigation,
|
||||
spec_version, is_latest_spec, workflow_spec_id, total_tasks, completed_tasks,
|
||||
last_updated, is_review, title):
|
||||
last_updated, is_review, title, study_id):
|
||||
self.id = id
|
||||
self.status = status
|
||||
self.next_task = next_task # The next task that requires user input.
|
||||
@ -204,13 +204,14 @@ class WorkflowApi(object):
|
||||
self.last_updated = last_updated
|
||||
self.title = title
|
||||
self.is_review = is_review
|
||||
self.study_id = study_id or ''
|
||||
|
||||
class WorkflowApiSchema(ma.Schema):
|
||||
class Meta:
|
||||
model = WorkflowApi
|
||||
fields = ["id", "status", "next_task", "navigation",
|
||||
"workflow_spec_id", "spec_version", "is_latest_spec", "total_tasks", "completed_tasks",
|
||||
"last_updated", "is_review", "title"]
|
||||
"last_updated", "is_review", "title", "study_id"]
|
||||
unknown = INCLUDE
|
||||
|
||||
status = EnumField(WorkflowStatus)
|
||||
@ -221,7 +222,7 @@ class WorkflowApiSchema(ma.Schema):
|
||||
def make_workflow(self, data, **kwargs):
|
||||
keys = ['id', 'status', 'next_task', 'navigation',
|
||||
'workflow_spec_id', 'spec_version', 'is_latest_spec', "total_tasks", "completed_tasks",
|
||||
"last_updated", "is_review", "title"]
|
||||
"last_updated", "is_review", "title", "study_id"]
|
||||
filtered_fields = {key: data[key] for key in keys}
|
||||
filtered_fields['next_task'] = TaskSchema().make_task(data['next_task'])
|
||||
return WorkflowApi(**filtered_fields)
|
||||
|
@ -5,12 +5,13 @@ from crc import db, ma
|
||||
from crc.models.study import StudyModel, StudySchema, WorkflowMetadataSchema, WorkflowMetadata
|
||||
from crc.models.workflow import WorkflowModel
|
||||
from crc.services.ldap_service import LdapService
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
class TaskEventModel(db.Model):
|
||||
__tablename__ = 'task_event'
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
study_id = db.Column(db.Integer, db.ForeignKey('study.id'), nullable=False)
|
||||
study_id = db.Column(db.Integer, db.ForeignKey('study.id'))
|
||||
user_uid = db.Column(db.String, nullable=False) # In some cases the unique user id may not exist in the db yet.
|
||||
workflow_id = db.Column(db.Integer, db.ForeignKey('workflow.id'), nullable=False)
|
||||
workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id'))
|
||||
@ -27,7 +28,7 @@ class TaskEventModel(db.Model):
|
||||
mi_count = db.Column(db.Integer)
|
||||
mi_index = db.Column(db.Integer)
|
||||
process_name = db.Column(db.String)
|
||||
date = db.Column(db.DateTime)
|
||||
date = db.Column(db.DateTime(timezone=True),default=func.now())
|
||||
|
||||
|
||||
class TaskEventModelSchema(SQLAlchemyAutoSchema):
|
||||
|
@ -3,6 +3,7 @@ import enum
|
||||
import marshmallow
|
||||
from marshmallow import EXCLUDE
|
||||
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema
|
||||
from sqlalchemy import func
|
||||
|
||||
from crc import db
|
||||
from crc.models.file import FileModel, FileDataModel
|
||||
@ -33,6 +34,7 @@ class WorkflowSpecModel(db.Model):
|
||||
category_id = db.Column(db.Integer, db.ForeignKey('workflow_spec_category.id'), nullable=True)
|
||||
category = db.relationship("WorkflowSpecCategoryModel")
|
||||
is_master_spec = db.Column(db.Boolean, default=False)
|
||||
standalone = db.Column(db.Boolean, default=False)
|
||||
|
||||
|
||||
class WorkflowSpecModelSchema(SQLAlchemyAutoSchema):
|
||||
@ -87,7 +89,8 @@ class WorkflowModel(db.Model):
|
||||
workflow_spec = db.relationship("WorkflowSpecModel")
|
||||
total_tasks = db.Column(db.Integer, default=0)
|
||||
completed_tasks = db.Column(db.Integer, default=0)
|
||||
last_updated = db.Column(db.DateTime)
|
||||
last_updated = db.Column(db.DateTime(timezone=True),default=func.now())
|
||||
user_id = db.Column(db.String, default=None)
|
||||
# Order By is important or generating hashes on reviews.
|
||||
dependencies = db.relationship(WorkflowSpecDependencyFile, cascade="all, delete, delete-orphan",
|
||||
order_by="WorkflowSpecDependencyFile.file_data_id")
|
||||
|
@ -76,7 +76,7 @@ class DataStoreBase(object):
|
||||
workflow_id=workflow_id,
|
||||
spec_id=workflow_spec_id)
|
||||
study.value = args[1]
|
||||
study.last_updated = datetime.now()
|
||||
study.last_updated = datetime.utcnow()
|
||||
overwritten = self.overwritten(study.value, prev_value)
|
||||
session.add(study)
|
||||
session.commit()
|
||||
|
@ -10,7 +10,9 @@ class FileDataGet(Script, DataStoreBase):
|
||||
return """Gets user data from the data store - takes only two keyword arguments arguments: 'file_id' and 'key' """
|
||||
|
||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
self.do_task(task, study_id, workflow_id, *args, **kwargs)
|
||||
if self.validate_kw_args(**kwargs):
|
||||
myargs = [kwargs['key']]
|
||||
return True
|
||||
|
||||
def validate_kw_args(self,**kwargs):
|
||||
if kwargs.get('key',None) is None:
|
||||
@ -26,6 +28,8 @@ class FileDataGet(Script, DataStoreBase):
|
||||
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
if self.validate_kw_args(**kwargs):
|
||||
myargs = [kwargs['key']]
|
||||
if 'default' in kwargs.keys():
|
||||
myargs.append(kwargs['default'])
|
||||
|
||||
return self.get_data_common(None,
|
||||
None,
|
||||
|
@ -10,7 +10,11 @@ class FileDataSet(Script, DataStoreBase):
|
||||
return """Sets data the data store - takes three keyword arguments arguments: 'file_id' and 'key' and 'value'"""
|
||||
|
||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
self.do_task(task, study_id, workflow_id, *args, **kwargs)
|
||||
if self.validate_kw_args(**kwargs):
|
||||
myargs = [kwargs['key'],kwargs['value']]
|
||||
fileid = kwargs['file_id']
|
||||
del(kwargs['file_id'])
|
||||
return True
|
||||
|
||||
def validate_kw_args(self,**kwargs):
|
||||
if kwargs.get('key',None) is None:
|
||||
|
@ -212,7 +212,7 @@ class FileService(object):
|
||||
|
||||
new_file_data_model = FileDataModel(
|
||||
data=binary_data, file_model_id=file_model.id, file_model=file_model,
|
||||
version=version, md5_hash=md5_checksum, date_created=datetime.now(),
|
||||
version=version, md5_hash=md5_checksum, date_created=datetime.utcnow(),
|
||||
size=size
|
||||
)
|
||||
session.add_all([file_model, new_file_data_model])
|
||||
|
@ -290,6 +290,7 @@ class StudyService(object):
|
||||
doc['files'] = []
|
||||
for file in doc_files:
|
||||
doc['files'].append({'file_id': file.id,
|
||||
'name': file.name,
|
||||
'workflow_id': file.workflow_id})
|
||||
|
||||
# update the document status to match the status of the workflow it is in.
|
||||
@ -495,8 +496,9 @@ class StudyService(object):
|
||||
def _create_workflow_model(study: StudyModel, spec):
|
||||
workflow_model = WorkflowModel(status=WorkflowStatus.not_started,
|
||||
study=study,
|
||||
user_id=None,
|
||||
workflow_spec_id=spec.id,
|
||||
last_updated=datetime.now())
|
||||
last_updated=datetime.utcnow())
|
||||
session.add(workflow_model)
|
||||
session.commit()
|
||||
return workflow_model
|
||||
|
@ -219,7 +219,7 @@ class WorkflowProcessor(object):
|
||||
self.workflow_model.status = self.get_status()
|
||||
self.workflow_model.total_tasks = len(tasks)
|
||||
self.workflow_model.completed_tasks = sum(1 for t in tasks if t.state in complete_states)
|
||||
self.workflow_model.last_updated = datetime.now()
|
||||
self.workflow_model.last_updated = datetime.utcnow()
|
||||
self.update_dependencies(self.spec_data_files)
|
||||
session.add(self.workflow_model)
|
||||
session.commit()
|
||||
|
@ -63,7 +63,7 @@ class WorkflowService(object):
|
||||
db.session.commit()
|
||||
workflow_model = WorkflowModel(status=WorkflowStatus.not_started,
|
||||
workflow_spec_id=spec_id,
|
||||
last_updated=datetime.now(),
|
||||
last_updated=datetime.utcnow(),
|
||||
study=study)
|
||||
return workflow_model
|
||||
|
||||
@ -408,7 +408,8 @@ class WorkflowService(object):
|
||||
completed_tasks=processor.workflow_model.completed_tasks,
|
||||
last_updated=processor.workflow_model.last_updated,
|
||||
is_review=is_review,
|
||||
title=spec.display_name
|
||||
title=spec.display_name,
|
||||
study_id=processor.workflow_model.study_id or None
|
||||
)
|
||||
if not next_task: # The Next Task can be requested to be a certain task, useful for parallel tasks.
|
||||
# This may or may not work, sometimes there is no next task to complete.
|
||||
@ -667,30 +668,39 @@ class WorkflowService(object):
|
||||
|
||||
@staticmethod
|
||||
def get_users_assigned_to_task(processor, spiff_task) -> List[str]:
|
||||
if not hasattr(spiff_task.task_spec, 'lane') or spiff_task.task_spec.lane is None:
|
||||
associated = StudyService.get_study_associates(processor.workflow_model.study.id)
|
||||
return [user['uid'] for user in associated if user['access']]
|
||||
if spiff_task.task_spec.lane not in spiff_task.data:
|
||||
return [] # No users are assignable to the task at this moment
|
||||
lane_users = spiff_task.data[spiff_task.task_spec.lane]
|
||||
if not isinstance(lane_users, list):
|
||||
lane_users = [lane_users]
|
||||
if processor.workflow_model.study_id is None and processor.workflow_model.user_id is None:
|
||||
raise ApiError.from_task(code='invalid_workflow',
|
||||
message='A workflow must have either a study_id or a user_id.',
|
||||
task=spiff_task)
|
||||
# Standalone workflow - we only care about the current user
|
||||
elif processor.workflow_model.study_id is None and processor.workflow_model.user_id is not None:
|
||||
return [processor.workflow_model.user_id]
|
||||
# Workflow associated with a study - get all the users
|
||||
else:
|
||||
if not hasattr(spiff_task.task_spec, 'lane') or spiff_task.task_spec.lane is None:
|
||||
associated = StudyService.get_study_associates(processor.workflow_model.study.id)
|
||||
return [user['uid'] for user in associated if user['access']]
|
||||
if spiff_task.task_spec.lane not in spiff_task.data:
|
||||
return [] # No users are assignable to the task at this moment
|
||||
lane_users = spiff_task.data[spiff_task.task_spec.lane]
|
||||
if not isinstance(lane_users, list):
|
||||
lane_users = [lane_users]
|
||||
|
||||
lane_uids = []
|
||||
for user in lane_users:
|
||||
if isinstance(user, dict):
|
||||
if 'value' in user and user['value'] is not None:
|
||||
lane_uids.append(user['value'])
|
||||
lane_uids = []
|
||||
for user in lane_users:
|
||||
if isinstance(user, dict):
|
||||
if 'value' in user and user['value'] is not None:
|
||||
lane_uids.append(user['value'])
|
||||
else:
|
||||
raise ApiError.from_task(code="task_lane_user_error", message="Spiff Task %s lane user dict must have a key called 'value' with the user's uid in it." %
|
||||
spiff_task.task_spec.name, task=spiff_task)
|
||||
elif isinstance(user, str):
|
||||
lane_uids.append(user)
|
||||
else:
|
||||
raise ApiError.from_task(code="task_lane_user_error", message="Spiff Task %s lane user dict must have a key called 'value' with the user's uid in it." %
|
||||
spiff_task.task_spec.name, task=spiff_task)
|
||||
elif isinstance(user, str):
|
||||
lane_uids.append(user)
|
||||
else:
|
||||
raise ApiError.from_task(code="task_lane_user_error", message="Spiff Task %s lane user is not a string or dict" %
|
||||
spiff_task.task_spec.name, task=spiff_task)
|
||||
raise ApiError.from_task(code="task_lane_user_error", message="Spiff Task %s lane user is not a string or dict" %
|
||||
spiff_task.task_spec.name, task=spiff_task)
|
||||
|
||||
return lane_uids
|
||||
return lane_uids
|
||||
|
||||
@staticmethod
|
||||
def log_task_action(user_uid, processor, spiff_task, action):
|
||||
@ -714,7 +724,7 @@ class WorkflowService(object):
|
||||
mi_count=task.multi_instance_count, # This is the number of times the task could repeat.
|
||||
mi_index=task.multi_instance_index, # And the index of the currently repeating task.
|
||||
process_name=task.process_name,
|
||||
date=datetime.now(),
|
||||
date=datetime.utcnow(),
|
||||
)
|
||||
db.session.add(task_event)
|
||||
db.session.commit()
|
||||
@ -783,3 +793,19 @@ class WorkflowService(object):
|
||||
for workflow in workflows:
|
||||
if workflow.status == WorkflowStatus.user_input_required or workflow.status == WorkflowStatus.waiting:
|
||||
WorkflowProcessor.reset(workflow, clear_data=False)
|
||||
|
||||
@staticmethod
|
||||
def get_workflow_from_spec(workflow_spec_id, user):
|
||||
workflow_model = WorkflowModel(status=WorkflowStatus.not_started,
|
||||
study=None,
|
||||
user_id=user.uid,
|
||||
workflow_spec_id=workflow_spec_id,
|
||||
last_updated=datetime.now())
|
||||
db.session.add(workflow_model)
|
||||
db.session.commit()
|
||||
return workflow_model
|
||||
|
||||
@staticmethod
|
||||
def get_standalone_workflow_specs():
|
||||
specs = db.session.query(WorkflowSpecModel).filter_by(standalone=True).all()
|
||||
return specs
|
||||
|
@ -266,7 +266,7 @@ class ExampleDataLoader:
|
||||
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):
|
||||
category_id=None, display_order=None, from_tests=False, standalone=False):
|
||||
"""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.
|
||||
returns an array of data models to be added to the database."""
|
||||
@ -278,7 +278,8 @@ class ExampleDataLoader:
|
||||
description=description,
|
||||
is_master_spec=master_spec,
|
||||
category_id=category_id,
|
||||
display_order=display_order)
|
||||
display_order=display_order,
|
||||
standalone=standalone)
|
||||
db.session.add(spec)
|
||||
db.session.commit()
|
||||
if not filepath and not from_tests:
|
||||
|
30
migrations/versions/8b976945a54e_.py
Normal file
30
migrations/versions/8b976945a54e_.py
Normal file
@ -0,0 +1,30 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 8b976945a54e
|
||||
Revises: c872232ebdcb
|
||||
Create Date: 2021-04-18 11:42:41.894378
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8b976945a54e'
|
||||
down_revision = 'c872232ebdcb'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('workflow', sa.Column('user_id', sa.String(), nullable=True))
|
||||
op.add_column('workflow_spec', sa.Column('standalone', sa.Boolean(), default=False))
|
||||
op.execute("UPDATE workflow_spec SET standalone=False WHERE standalone is null;")
|
||||
op.execute("ALTER TABLE task_event ALTER COLUMN study_id DROP NOT NULL")
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.execute("UPDATE workflow SET user_id=NULL WHERE user_id is not NULL")
|
||||
op.drop_column('workflow', 'user_id')
|
||||
op.drop_column('workflow_spec', 'standalone')
|
||||
op.execute("ALTER TABLE task_event ALTER COLUMN study_id SET NOT NULL ")
|
@ -0,0 +1,28 @@
|
||||
"""update type on task_events table and workflow table
|
||||
|
||||
Revision ID: abeffe547305
|
||||
Revises: 665624ac29f1
|
||||
Create Date: 2021-04-28 08:51:16.220260
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'abeffe547305'
|
||||
down_revision = '665624ac29f1'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.execute("alter table task_event alter column date type timestamp with time zone")
|
||||
op.execute("alter table workflow alter column last_updated type timestamp with time zone")
|
||||
pass
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.execute("alter table task_event alter column date type timestamp without time zone")
|
||||
op.execute("alter table workflow alter column last_updated type timestamp without time zone")
|
||||
pass
|
@ -70,7 +70,7 @@ class BaseTest(unittest.TestCase):
|
||||
{
|
||||
'id': 0,
|
||||
'title': 'The impact of fried pickles on beer consumption in bipedal software developers.',
|
||||
'last_updated': datetime.datetime.now(),
|
||||
'last_updated': datetime.datetime.utcnow(),
|
||||
'status': StudyStatus.in_progress,
|
||||
'primary_investigator_id': 'dhf8r',
|
||||
'sponsor': 'Sartography Pharmaceuticals',
|
||||
@ -80,7 +80,7 @@ class BaseTest(unittest.TestCase):
|
||||
{
|
||||
'id': 1,
|
||||
'title': 'Requirement of hippocampal neurogenesis for the behavioral effects of soft pretzels',
|
||||
'last_updated': datetime.datetime.now(),
|
||||
'last_updated': datetime.datetime.utcnow(),
|
||||
'status': StudyStatus.in_progress,
|
||||
'primary_investigator_id': 'dhf8r',
|
||||
'sponsor': 'Makerspace & Co.',
|
||||
@ -175,11 +175,6 @@ class BaseTest(unittest.TestCase):
|
||||
specs = session.query(WorkflowSpecModel).all()
|
||||
self.assertIsNotNone(specs)
|
||||
|
||||
for spec in specs:
|
||||
files = session.query(FileModel).filter_by(workflow_spec_id=spec.id).all()
|
||||
self.assertIsNotNone(files)
|
||||
self.assertGreater(len(files), 0)
|
||||
|
||||
for spec in specs:
|
||||
files = session.query(FileModel).filter_by(workflow_spec_id=spec.id).all()
|
||||
self.assertIsNotNone(files)
|
||||
@ -379,6 +374,10 @@ class BaseTest(unittest.TestCase):
|
||||
|
||||
def complete_form(self, workflow_in, task_in, dict_data, update_all=False, error_code=None, terminate_loop=None,
|
||||
user_uid="dhf8r"):
|
||||
# workflow_in should be a workflow, not a workflow_api
|
||||
# we were passing in workflow_api in many of our tests, and
|
||||
# this caused problems testing standalone workflows
|
||||
standalone = getattr(workflow_in.workflow_spec, 'standalone', False)
|
||||
prev_completed_task_count = workflow_in.completed_tasks
|
||||
if isinstance(task_in, dict):
|
||||
task_id = task_in["id"]
|
||||
@ -421,7 +420,8 @@ class BaseTest(unittest.TestCase):
|
||||
.order_by(TaskEventModel.date.desc()).all()
|
||||
self.assertGreater(len(task_events), 0)
|
||||
event = task_events[0]
|
||||
self.assertIsNotNone(event.study_id)
|
||||
if not standalone:
|
||||
self.assertIsNotNone(event.study_id)
|
||||
self.assertEqual(user_uid, event.user_uid)
|
||||
self.assertEqual(workflow.id, event.workflow_id)
|
||||
self.assertEqual(workflow.workflow_spec_id, event.workflow_spec_id)
|
||||
|
@ -21,16 +21,21 @@ fileid = documents['UVACompl_PRCAppr'].files[0]['file_id']
|
||||
file_data_set(file_id=fileid,key='test',value='me')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:endEvent id="Event_1pdyoyv">
|
||||
<bpmn:incoming>Flow_15mmymi</bpmn:incoming>
|
||||
<bpmn:incoming>Flow_02bgcrp</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_0z7kamo" sourceRef="Activity_0yikdu7" targetRef="Activity_19x6e2e" />
|
||||
<bpmn:sequenceFlow id="Flow_15mmymi" sourceRef="Activity_19x6e2e" targetRef="Event_1pdyoyv" />
|
||||
<bpmn:sequenceFlow id="Flow_15mmymi" sourceRef="Activity_19x6e2e" targetRef="Activity_0oaeqxs" />
|
||||
<bpmn:scriptTask id="Activity_19x6e2e" name="get output">
|
||||
<bpmn:incoming>Flow_0z7kamo</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_15mmymi</bpmn:outgoing>
|
||||
<bpmn:script>output=file_data_get(file_id=fileid,key='test')
|
||||
</bpmn:script>
|
||||
<bpmn:script>output=file_data_get(file_id=fileid,key='test')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:scriptTask id="Activity_0oaeqxs" name="get output2">
|
||||
<bpmn:incoming>Flow_15mmymi</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_02bgcrp</bpmn:outgoing>
|
||||
<bpmn:script>output2=file_data_get(file_id=fileid,key='unobtainium',default='nope')</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_02bgcrp" sourceRef="Activity_0oaeqxs" targetRef="Event_1pdyoyv" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_18biih5">
|
||||
@ -52,7 +57,7 @@ file_data_set(file_id=fileid,key='test',value='me')</bpmn:script>
|
||||
<dc:Bounds x="430" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_1pdyoyv_di" bpmnElement="Event_1pdyoyv">
|
||||
<dc:Bounds x="782" y="99" width="36" height="36" />
|
||||
<dc:Bounds x="962" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_0z7kamo_di" bpmnElement="Flow_0z7kamo">
|
||||
<di:waypoint x="530" y="117" />
|
||||
@ -60,11 +65,18 @@ file_data_set(file_id=fileid,key='test',value='me')</bpmn:script>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_15mmymi_di" bpmnElement="Flow_15mmymi">
|
||||
<di:waypoint x="690" y="117" />
|
||||
<di:waypoint x="782" y="117" />
|
||||
<di:waypoint x="760" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Activity_0ma7ela_di" bpmnElement="Activity_19x6e2e">
|
||||
<dc:Bounds x="590" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_0oaeqxs_di" bpmnElement="Activity_0oaeqxs">
|
||||
<dc:Bounds x="760" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="Flow_02bgcrp_di" bpmnElement="Flow_02bgcrp">
|
||||
<di:waypoint x="860" y="117" />
|
||||
<di:waypoint x="962" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
||||
|
58
tests/data/hello_world/hello_world.bpmn
Normal file
58
tests/data/hello_world/hello_world.bpmn
Normal file
@ -0,0 +1,58 @@
|
||||
<?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_0ixyfs0" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.5.0">
|
||||
<bpmn:process id="Process_HelloWorld" name="Hello World Process" isExecutable="true">
|
||||
<bpmn:documentation>This workflow asks for a name and says hello</bpmn:documentation>
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_0qyd2b7</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0qyd2b7" sourceRef="StartEvent_1" targetRef="Task_GetName" />
|
||||
<bpmn:userTask id="Task_GetName" name="Get Name" camunda:formKey="Name">
|
||||
<bpmn:documentation>Hello</bpmn:documentation>
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="name" label="Name" type="string" defaultValue="World" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>SequenceFlow_0qyd2b7</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_1h46b40</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_1h46b40" sourceRef="Task_GetName" targetRef="Task_SayHello" />
|
||||
<bpmn:manualTask id="Task_SayHello" name="Say Hello">
|
||||
<bpmn:documentation>Hello {{name}}</bpmn:documentation>
|
||||
<bpmn:incoming>SequenceFlow_1h46b40</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_0lqrc6e</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:endEvent id="EndEvent_1l03lqw">
|
||||
<bpmn:incoming>SequenceFlow_0lqrc6e</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_0lqrc6e" sourceRef="Task_SayHello" targetRef="EndEvent_1l03lqw" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_HelloWorld">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0qyd2b7_di" bpmnElement="SequenceFlow_0qyd2b7">
|
||||
<di:waypoint x="215" y="117" />
|
||||
<di:waypoint x="270" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="UserTask_0fbucz7_di" bpmnElement="Task_GetName">
|
||||
<dc:Bounds x="270" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1h46b40_di" bpmnElement="SequenceFlow_1h46b40">
|
||||
<di:waypoint x="370" y="117" />
|
||||
<di:waypoint x="430" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="ManualTask_1tia2zr_di" bpmnElement="Task_SayHello">
|
||||
<dc:Bounds x="430" y="77" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="EndEvent_1l03lqw_di" bpmnElement="EndEvent_1l03lqw">
|
||||
<dc:Bounds x="592" y="99" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_0lqrc6e_di" bpmnElement="SequenceFlow_0lqrc6e">
|
||||
<di:waypoint x="530" y="117" />
|
||||
<di:waypoint x="592" y="117" />
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
@ -25,7 +25,7 @@ class TestStudyApi(BaseTest):
|
||||
TEST_STUDY = {
|
||||
"title": "Phase III Trial of Genuine People Personalities (GPP) Autonomous Intelligent Emotional Agents "
|
||||
"for Interstellar Spacecraft",
|
||||
"last_updated": datetime.now(tz=timezone.utc),
|
||||
"last_updated": datetime.utcnow(),
|
||||
"primary_investigator_id": "tmm2x",
|
||||
"user_uid": "dhf8r",
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ class TestStudyCancellations(BaseTest):
|
||||
workflow, study_id = self.load_workflow()
|
||||
workflow_api, first_task = self.get_first_task(workflow)
|
||||
|
||||
self.complete_form(workflow_api, first_task, {})
|
||||
self.complete_form(workflow, first_task, {})
|
||||
|
||||
study_result = self.put_study_on_hold(study_id)
|
||||
self.assertEqual('New Title', study_result.title)
|
||||
@ -82,10 +82,10 @@ class TestStudyCancellations(BaseTest):
|
||||
workflow, study_id = self.load_workflow()
|
||||
workflow_api, first_task = self.get_first_task(workflow)
|
||||
|
||||
self.complete_form(workflow_api, first_task, {})
|
||||
self.complete_form(workflow, first_task, {})
|
||||
|
||||
workflow_api, next_task = self.get_second_task(workflow)
|
||||
self.complete_form(workflow_api, next_task, {'how_many': 3})
|
||||
self.complete_form(workflow, next_task, {'how_many': 3})
|
||||
|
||||
study_result = self.put_study_on_hold(study_id)
|
||||
self.assertEqual('Second Title', study_result.title)
|
||||
@ -95,13 +95,13 @@ class TestStudyCancellations(BaseTest):
|
||||
workflow, study_id = self.load_workflow()
|
||||
workflow_api, first_task = self.get_first_task(workflow)
|
||||
|
||||
self.complete_form(workflow_api, first_task, {})
|
||||
self.complete_form(workflow, first_task, {})
|
||||
|
||||
workflow_api, second_task = self.get_second_task(workflow)
|
||||
self.complete_form(workflow_api, second_task, {'how_many': 3})
|
||||
self.complete_form(workflow, second_task, {'how_many': 3})
|
||||
|
||||
workflow_api, third_task = self.get_third_task(workflow)
|
||||
self.complete_form(workflow_api, third_task, {})
|
||||
self.complete_form(workflow, third_task, {})
|
||||
|
||||
study_result = self.put_study_on_hold(study_id)
|
||||
self.assertEqual('Beer consumption in the bipedal software engineer', study_result.title)
|
||||
|
@ -47,7 +47,7 @@ class TestStudyService(BaseTest):
|
||||
|
||||
self.assertIsNotNone(study.id)
|
||||
workflow = WorkflowModel(workflow_spec_id="random_fact", study_id=study.id,
|
||||
status=WorkflowStatus.not_started, last_updated=datetime.now())
|
||||
status=WorkflowStatus.not_started, last_updated=datetime.utcnow())
|
||||
db.session.add(workflow)
|
||||
db.session.commit()
|
||||
# Assure there is a master specification, one standard spec, and lookup tables.
|
||||
|
@ -273,7 +273,7 @@ class TestAuthentication(BaseTest):
|
||||
def _make_fake_study(self, uid):
|
||||
return {
|
||||
"title": "blah",
|
||||
"last_updated": datetime.now(tz=timezone.utc),
|
||||
"last_updated": datetime.utcnow(),
|
||||
"status": StudyStatus.in_progress,
|
||||
"primary_investigator_id": uid,
|
||||
"user_uid": uid,
|
||||
|
@ -13,7 +13,8 @@ class TestAutoSetPrimaryBPMN(BaseTest):
|
||||
category_id = session.query(WorkflowSpecCategoryModel).first().id
|
||||
# Add a workflow spec
|
||||
spec = WorkflowSpecModel(id='make_cookies', name='make_cookies', display_name='Cooooookies',
|
||||
description='Om nom nom delicious cookies', category_id=category_id)
|
||||
description='Om nom nom delicious cookies', category_id=category_id,
|
||||
standalone=False)
|
||||
rv = self.app.post('/v1.0/workflow-specification',
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json",
|
||||
|
@ -23,7 +23,6 @@ class TestEmailScript(BaseTest):
|
||||
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.complete_form(workflow, first_task, {'subject': 'My Email Subject', 'recipients': 'test@example.com'})
|
||||
|
||||
self.assertEqual(1, len(outbox))
|
||||
@ -49,7 +48,6 @@ class TestEmailScript(BaseTest):
|
||||
def test_bad_email_address_1(self):
|
||||
workflow = self.create_workflow('email_script')
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
self.complete_form(workflow, first_task, {'recipients': 'test@example'})
|
||||
@ -57,7 +55,6 @@ class TestEmailScript(BaseTest):
|
||||
def test_bad_email_address_2(self):
|
||||
workflow = self.create_workflow('email_script')
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
self.complete_form(workflow, first_task, {'recipients': 'test'})
|
||||
|
@ -27,5 +27,6 @@ class TestFileDatastore(BaseTest):
|
||||
processor = WorkflowProcessor(workflow)
|
||||
processor.do_engine_steps()
|
||||
task_data = processor.bpmn_workflow.last_task.data
|
||||
self.assertEqual(task_data['output'],'me')
|
||||
self.assertEqual(task_data['output'], 'me')
|
||||
self.assertEqual(task_data['output2'], 'nope')
|
||||
|
||||
|
23
tests/test_launch_workflow_outside_study.py
Normal file
23
tests/test_launch_workflow_outside_study.py
Normal file
@ -0,0 +1,23 @@
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
from crc import session
|
||||
from crc.models.user import UserModel
|
||||
from crc.services.workflow_service import WorkflowService
|
||||
|
||||
from example_data import ExampleDataLoader
|
||||
|
||||
|
||||
class TestNoStudyWorkflow(BaseTest):
|
||||
|
||||
def test_no_study_workflow(self):
|
||||
self.load_example_data()
|
||||
spec = ExampleDataLoader().create_spec('hello_world', 'Hello World', standalone=True, from_tests=True)
|
||||
user = session.query(UserModel).first()
|
||||
self.assertIsNotNone(user)
|
||||
workflow_model = WorkflowService.get_workflow_from_spec(spec.id, user)
|
||||
workflow_api = self.get_workflow_api(workflow_model)
|
||||
first_task = workflow_api.next_task
|
||||
self.complete_form(workflow_model, first_task, {'name': 'Big Guy'})
|
||||
workflow_api = self.get_workflow_api(workflow_model)
|
||||
second_task = workflow_api.next_task
|
||||
self.assertEqual(second_task.documentation, 'Hello Big Guy')
|
@ -13,10 +13,10 @@ class TestMessageEvent(BaseTest):
|
||||
# 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)
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Activity_HowMany', workflow_api.next_task.name)
|
||||
|
||||
# reset the workflow
|
||||
# this ultimately calls crc.api.workflow.set_current_task
|
||||
|
@ -67,14 +67,14 @@ class TestMultiinstanceTasksApi(BaseTest):
|
||||
content_type="application/json")
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
workflow = WorkflowApiSchema().load(json_data)
|
||||
data = workflow.next_task.data
|
||||
workflow_api = WorkflowApiSchema().load(json_data)
|
||||
data = workflow_api.next_task.data
|
||||
data['investigator']['email'] = "dhf8r@virginia.edu"
|
||||
self.complete_form(workflow, workflow.next_task, data)
|
||||
self.complete_form(workflow, workflow_api.next_task, data)
|
||||
#tasks = self.get_workflow_api(workflow).user_tasks
|
||||
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.assertEqual(WorkflowStatus.complete, workflow.status)
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual(WorkflowStatus.complete, workflow_api.status)
|
||||
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
|
@ -386,15 +386,15 @@ class TestTasksApi(BaseTest):
|
||||
# Start the workflow.
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
self.complete_form(workflow, first_task, {"has_bananas": True})
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Task_Num_Bananas', workflow.next_task.name)
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Task_Num_Bananas', workflow_api.next_task.name)
|
||||
|
||||
# Trying to re-submit the initial task, and answer differently, should result in an error.
|
||||
self.complete_form(workflow, first_task, {"has_bananas": False}, error_code="invalid_state")
|
||||
|
||||
# Go ahead and set the number of bananas.
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
task = workflow.next_task
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
task = workflow_api.next_task
|
||||
|
||||
self.complete_form(workflow, task, {"num_bananas": 4})
|
||||
# We are now at the end of the workflow.
|
||||
@ -405,19 +405,19 @@ class TestTasksApi(BaseTest):
|
||||
content_type="application/json")
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
workflow = WorkflowApiSchema().load(json_data)
|
||||
workflow_api = WorkflowApiSchema().load(json_data)
|
||||
|
||||
# Assure the Next Task is the one we just reset the token to be on.
|
||||
self.assertEqual("Task_Has_Bananas", workflow.next_task.name)
|
||||
self.assertEqual("Task_Has_Bananas", workflow_api.next_task.name)
|
||||
|
||||
# Go ahead and get that workflow one more time, it should still be right.
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
|
||||
# Assure the Next Task is the one we just reset the token to be on.
|
||||
self.assertEqual("Task_Has_Bananas", workflow.next_task.name)
|
||||
self.assertEqual("Task_Has_Bananas", workflow_api.next_task.name)
|
||||
|
||||
# The next task should be a different value.
|
||||
self.complete_form(workflow, workflow.next_task, {"has_bananas": False})
|
||||
workflow = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Task_Why_No_Bananas', workflow.next_task.name)
|
||||
self.complete_form(workflow, workflow_api.next_task, {"has_bananas": False})
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Task_Why_No_Bananas', workflow_api.next_task.name)
|
||||
|
||||
|
@ -52,7 +52,7 @@ class TestWorkflowSync(BaseTest):
|
||||
self.load_example_data()
|
||||
othersys = get_all_spec_state()
|
||||
rf2pos = get_random_fact_pos(othersys)
|
||||
othersys[rf2pos]['date_created'] = str(datetime.now())
|
||||
othersys[rf2pos]['date_created'] = str(datetime.utcnow())
|
||||
othersys[rf2pos]['md5_hash'] = '12345'
|
||||
mock_get.return_value = othersys
|
||||
response = get_changed_workflows('localhost:0000') #endpoint is not used due to mock
|
||||
@ -69,7 +69,7 @@ class TestWorkflowSync(BaseTest):
|
||||
self.load_example_data()
|
||||
othersys = get_all_spec_state()
|
||||
othersys.append({'workflow_spec_id':'my_new_workflow',
|
||||
'date_created':str(datetime.now()),
|
||||
'date_created':str(datetime.utcnow()),
|
||||
'md5_hash': '12345'})
|
||||
mock_get.return_value = othersys
|
||||
response = get_changed_workflows('localhost:0000') #endpoint is not used due to mock
|
||||
@ -121,7 +121,7 @@ class TestWorkflowSync(BaseTest):
|
||||
self.load_example_data()
|
||||
othersys = get_workflow_spec_files('random_fact')
|
||||
rf2pos = get_random_fact_2_pos(othersys)
|
||||
othersys[rf2pos]['date_created'] = str(datetime.now())
|
||||
othersys[rf2pos]['date_created'] = str(datetime.utcnow())
|
||||
othersys[rf2pos]['md5_hash'] = '12345'
|
||||
mock_get.return_value = othersys
|
||||
response = get_changed_files('localhost:0000','random_fact',as_df=False) #endpoint is not used due to mock
|
||||
@ -145,7 +145,7 @@ class TestWorkflowSync(BaseTest):
|
||||
# change the remote file date and hash
|
||||
othersys = get_workflow_spec_files('random_fact')
|
||||
rf2pos = get_random_fact_2_pos(othersys)
|
||||
othersys[rf2pos]['date_created'] = str(datetime.now())
|
||||
othersys[rf2pos]['date_created'] = str(datetime.utcnow())
|
||||
othersys[rf2pos]['md5_hash'] = '12345'
|
||||
spec_files_mock.return_value = othersys
|
||||
# actually go get a different file
|
||||
@ -179,7 +179,7 @@ class TestWorkflowSync(BaseTest):
|
||||
'primary':False,
|
||||
'content_type':'text/text',
|
||||
'primary_process_id':None,
|
||||
'date_created':str(datetime.now()),
|
||||
'date_created':str(datetime.utcnow()),
|
||||
'md5_hash':'12345'
|
||||
}
|
||||
othersys.append(newfile)
|
||||
|
@ -7,7 +7,7 @@ class TestBooleanDefault(BaseTest):
|
||||
workflow = self.create_workflow('boolean_default_value')
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
set_default_task = workflow_api.next_task
|
||||
result = self.complete_form(workflow_api, set_default_task, {'yes_no': yes_no})
|
||||
result = self.complete_form(workflow, set_default_task, {'yes_no': yes_no})
|
||||
return result
|
||||
|
||||
def test_boolean_true_string(self):
|
||||
|
@ -7,35 +7,35 @@ class TestWorkflowEnumDefault(BaseTest):
|
||||
def test_enum_default_from_value_expression(self):
|
||||
workflow = self.create_workflow('enum_value_expression')
|
||||
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
self.assertEqual('Activity_UserInput', first_task.name)
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
first_task = workflow_api.next_task
|
||||
self.assertEqual('Activity_UserInput', first_task.name)
|
||||
|
||||
result = self.complete_form(workflow_api, first_task, {'user_input': True})
|
||||
result = self.complete_form(workflow, first_task, {'user_input': True})
|
||||
self.assertIn('user_input', result.next_task.data)
|
||||
self.assertEqual(True, result.next_task.data['user_input'])
|
||||
self.assertIn('lookup_output', result.next_task.data)
|
||||
self.assertEqual('black', result.next_task.data['lookup_output'])
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Activity_PickColor', self.get_workflow_api(workflow_api).next_task.name)
|
||||
self.assertEqual('Activity_PickColor', workflow_api.next_task.name)
|
||||
self.assertEqual({'value': 'black', 'label': 'Black'}, workflow_api.next_task.data['color_select'])
|
||||
|
||||
#
|
||||
workflow = self.create_workflow('enum_value_expression')
|
||||
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
self.assertEqual('Activity_UserInput', first_task.name)
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
first_task = workflow_api.next_task
|
||||
self.assertEqual('Activity_UserInput', first_task.name)
|
||||
|
||||
result = self.complete_form(workflow_api, first_task, {'user_input': False})
|
||||
result = self.complete_form(workflow, first_task, {'user_input': False})
|
||||
self.assertIn('user_input', result.next_task.data)
|
||||
self.assertEqual(False, result.next_task.data['user_input'])
|
||||
self.assertIn('lookup_output', result.next_task.data)
|
||||
self.assertEqual('white', result.next_task.data['lookup_output'])
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Activity_PickColor', self.get_workflow_api(workflow_api).next_task.name)
|
||||
self.assertEqual('Activity_PickColor', workflow_api.next_task.name)
|
||||
self.assertEqual({'value': 'white', 'label': 'White'}, workflow_api.next_task.data['color_select'])
|
||||
|
||||
def test_enum_value_expression_and_default(self):
|
||||
|
@ -18,7 +18,7 @@ class TestFormFieldName(BaseTest):
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
first_task = workflow_api.next_task
|
||||
self.complete_form(workflow_api, first_task, {})
|
||||
self.complete_form(workflow, first_task, {})
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
second_task = workflow_api.next_task
|
||||
|
@ -34,14 +34,13 @@ class TestWorkflowHiddenRequiredField(BaseTest):
|
||||
|
||||
first_task = workflow_api.next_task
|
||||
self.assertEqual('Activity_Hello', first_task.name)
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
|
||||
self.complete_form(workflow_api, first_task, {})
|
||||
self.complete_form(workflow, first_task, {})
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
|
||||
second_task = workflow_api.next_task
|
||||
self.assertEqual('Activity_HiddenField', second_task.name)
|
||||
self.complete_form(workflow_api, second_task, {})
|
||||
self.complete_form(workflow, second_task, {})
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
|
||||
# The color field is hidden and required. Make sure we use the default value
|
||||
|
@ -12,20 +12,20 @@ class TestWorkflowRestart(BaseTest):
|
||||
|
||||
workflow = self.create_workflow('message_event')
|
||||
|
||||
first_task = self.get_workflow_api(workflow).next_task
|
||||
self.assertEqual('Activity_GetData', first_task.name)
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
first_task = workflow_api.next_task
|
||||
self.assertEqual('Activity_GetData', first_task.name)
|
||||
|
||||
result = self.complete_form(workflow_api, first_task, {'formdata': 'asdf'})
|
||||
result = self.complete_form(workflow, first_task, {'formdata': 'asdf'})
|
||||
self.assertIn('formdata', result.next_task.data)
|
||||
self.assertEqual('asdf', result.next_task.data['formdata'])
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Activity_HowMany', self.get_workflow_api(workflow_api).next_task.name)
|
||||
self.assertEqual('Activity_HowMany', workflow_api.next_task.name)
|
||||
|
||||
# restart with data. should land at beginning with data
|
||||
workflow_api = self.restart_workflow_api(result)
|
||||
first_task = self.get_workflow_api(workflow_api).next_task
|
||||
first_task = workflow_api.next_task
|
||||
self.assertEqual('Activity_GetData', first_task.name)
|
||||
self.assertIn('formdata', workflow_api.next_task.data)
|
||||
self.assertEqual('asdf', workflow_api.next_task.data['formdata'])
|
||||
@ -36,7 +36,6 @@ class TestWorkflowRestart(BaseTest):
|
||||
self.assertEqual('Activity_GetData', first_task.name)
|
||||
self.assertNotIn('formdata', workflow_api.next_task.data)
|
||||
|
||||
|
||||
def test_workflow_restart_delete_files(self):
|
||||
self.load_example_data()
|
||||
irb_code = 'Study_Protocol_Document'
|
||||
@ -80,14 +79,14 @@ class TestWorkflowRestart(BaseTest):
|
||||
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_api = self.get_workflow_api(workflow)
|
||||
self.complete_form(workflow_api, first_task, {'formdata': 'asdf'})
|
||||
first_task = workflow_api.next_task
|
||||
self.assertEqual('Activity_GetData', first_task.name)
|
||||
self.complete_form(workflow, first_task, {'formdata': 'asdf'})
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
self.assertEqual('Activity_HowMany', workflow_api.next_task.name)
|
||||
|
||||
workflow_api = self.restart_workflow_api(workflow)
|
||||
self.restart_workflow_api(workflow)
|
||||
study_result = session.query(StudyModel).filter(StudyModel.id == study_id).first()
|
||||
self.assertEqual('New Title', study_result.title)
|
||||
|
||||
@ -106,17 +105,16 @@ class TestWorkflowRestart(BaseTest):
|
||||
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_api = self.get_workflow_api(workflow)
|
||||
self.complete_form(workflow_api, first_task, {'formdata': 'asdf'})
|
||||
first_task = workflow_api.next_task
|
||||
self.assertEqual('Activity_GetData', first_task.name)
|
||||
self.complete_form(workflow, first_task, {'formdata': 'asdf'})
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
next_task = workflow_api.next_task
|
||||
self.assertEqual('Activity_HowMany', next_task.name)
|
||||
self.complete_form(workflow_api, next_task, {'how_many': 3})
|
||||
self.complete_form(workflow, next_task, {'how_many': 3})
|
||||
|
||||
workflow_api = self.restart_workflow_api(workflow)
|
||||
study_result = session.query(StudyModel).filter(StudyModel.id == study_id).first()
|
||||
self.assertEqual('Beer consumption in the bipedal software engineer', study_result.title)
|
||||
|
||||
|
@ -3,7 +3,9 @@ import json
|
||||
from tests.base_test import BaseTest
|
||||
from crc import session
|
||||
from crc.models.file import FileModel
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowSpecCategoryModel
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowSpecCategoryModel, WorkflowSpecCategoryModelSchema
|
||||
|
||||
from example_data import ExampleDataLoader
|
||||
|
||||
|
||||
class TestWorkflowSpec(BaseTest):
|
||||
@ -28,7 +30,8 @@ class TestWorkflowSpec(BaseTest):
|
||||
category_id = session.query(WorkflowSpecCategoryModel).first().id
|
||||
category_count = session.query(WorkflowSpecModel).filter_by(category_id=category_id).count()
|
||||
spec = WorkflowSpecModel(id='make_cookies', name='make_cookies', display_name='Cooooookies',
|
||||
description='Om nom nom delicious cookies', category_id=category_id)
|
||||
description='Om nom nom delicious cookies', category_id=category_id,
|
||||
standalone=False)
|
||||
rv = self.app.post('/v1.0/workflow-specification',
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json",
|
||||
@ -101,3 +104,60 @@ class TestWorkflowSpec(BaseTest):
|
||||
num_workflows_after = session.query(WorkflowModel).filter_by(workflow_spec_id=spec_id).count()
|
||||
self.assertEqual(num_files_after + num_workflows_after, 0)
|
||||
|
||||
def test_get_standalone_workflow_specs(self):
|
||||
self.load_example_data()
|
||||
category = session.query(WorkflowSpecCategoryModel).first()
|
||||
ExampleDataLoader().create_spec('hello_world', 'Hello World', category_id=category.id,
|
||||
standalone=True, from_tests=True)
|
||||
rv = self.app.get('/v1.0/workflow-specification/standalone', headers=self.logged_in_headers())
|
||||
self.assertEqual(1, len(rv.json))
|
||||
|
||||
ExampleDataLoader().create_spec('email_script', 'Email Script', category_id=category.id,
|
||||
standalone=True, from_tests=True)
|
||||
|
||||
rv = self.app.get('/v1.0/workflow-specification/standalone', headers=self.logged_in_headers())
|
||||
self.assertEqual(2, len(rv.json))
|
||||
|
||||
def test_get_workflow_from_workflow_spec(self):
|
||||
self.load_example_data()
|
||||
spec = ExampleDataLoader().create_spec('hello_world', 'Hello World', standalone=True, from_tests=True)
|
||||
rv = self.app.post(f'/v1.0/workflow-specification/{spec.id}', headers=self.logged_in_headers())
|
||||
self.assert_success(rv)
|
||||
self.assertEqual('hello_world', rv.json['workflow_spec_id'])
|
||||
self.assertEqual('Task_GetName', rv.json['next_task']['name'])
|
||||
|
||||
def test_add_workflow_spec_category(self):
|
||||
self.load_example_data()
|
||||
count = session.query(WorkflowSpecCategoryModel).count()
|
||||
category = WorkflowSpecCategoryModel(
|
||||
id=count,
|
||||
name='another_test_category',
|
||||
display_name='Another Test Category',
|
||||
display_order=0
|
||||
)
|
||||
rv = self.app.post(f'/v1.0/workflow-specification-category',
|
||||
headers=self.logged_in_headers(),
|
||||
content_type="application/json",
|
||||
data=json.dumps(WorkflowSpecCategoryModelSchema().dump(category))
|
||||
)
|
||||
self.assert_success(rv)
|
||||
result = session.query(WorkflowSpecCategoryModel).filter(WorkflowSpecCategoryModel.name=='another_test_category').first()
|
||||
self.assertEqual('Another Test Category', result.display_name)
|
||||
self.assertEqual(count, result.id)
|
||||
|
||||
def test_update_workflow_spec_category(self):
|
||||
self.load_example_data()
|
||||
category = session.query(WorkflowSpecCategoryModel).first()
|
||||
category_name_before = category.name
|
||||
new_category_name = category_name_before + '_asdf'
|
||||
self.assertNotEqual(category_name_before, new_category_name)
|
||||
|
||||
category.name = new_category_name
|
||||
|
||||
rv = self.app.put(f'/v1.0/workflow-specification-category/{category.id}',
|
||||
content_type="application/json",
|
||||
headers=self.logged_in_headers(),
|
||||
data=json.dumps(WorkflowSpecCategoryModelSchema().dump(category)))
|
||||
self.assert_success(rv)
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
self.assertEqual(new_category_name, json_data['name'])
|
||||
|
@ -9,7 +9,7 @@ class TestValueExpression(BaseTest):
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
first_task = workflow_api.next_task
|
||||
self.complete_form(workflow_api, first_task, {'value_expression_value': ''})
|
||||
self.complete_form(workflow, first_task, {'value_expression_value': ''})
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
second_task = workflow_api.next_task
|
||||
@ -26,7 +26,7 @@ class TestValueExpression(BaseTest):
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
first_task = workflow_api.next_task
|
||||
self.complete_form(workflow_api, first_task, {'value_expression_value': 'black'})
|
||||
self.complete_form(workflow, first_task, {'value_expression_value': 'black'})
|
||||
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
second_task = workflow_api.next_task
|
||||
|
Loading…
x
Reference in New Issue
Block a user