Merge branch 'dev' into feature/performance-refactor

This commit is contained in:
Dan Funk 2021-05-04 11:30:13 -04:00 committed by GitHub
commit 34759a2f3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 426 additions and 130 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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")

View File

@ -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()

View File

@ -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,

View File

@ -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:

View File

@ -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])

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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:

View 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 ")

View File

@ -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

View File

@ -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)

View File

@ -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>

View 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>

View File

@ -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",
}

View File

@ -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)

View File

@ -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.

View File

@ -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,

View File

@ -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",

View File

@ -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'})

View File

@ -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')

View 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')

View File

@ -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

View File

@ -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')

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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'])

View File

@ -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