mirror of
https://github.com/sartography/cr-connect-workflow.git
synced 2025-02-22 20:58:28 +00:00
Prefer tasks that share a parent over just the the next available task when returning the next_task in the workflow processor.
This commit is contained in:
parent
ef7ee284b2
commit
59f605c3df
@ -57,7 +57,7 @@ def get_document_directory(study_id, workflow_id=None):
|
|||||||
if file.irb_doc_code in doc_dict:
|
if file.irb_doc_code in doc_dict:
|
||||||
doc_code = doc_dict[file.irb_doc_code]
|
doc_code = doc_dict[file.irb_doc_code]
|
||||||
else:
|
else:
|
||||||
doc_code = {'category1': "Unknown", 'category2': None, 'category3': None}
|
doc_code = {'category1': "Unknown", 'category2': '', 'category3': ''}
|
||||||
if workflow_id:
|
if workflow_id:
|
||||||
expand = file.workflow_id == int(workflow_id)
|
expand = file.workflow_id == int(workflow_id)
|
||||||
else:
|
else:
|
||||||
|
@ -244,6 +244,7 @@ def __update_task(processor, task, data, user):
|
|||||||
here because we need to do it multiple times when completing all tasks in
|
here because we need to do it multiple times when completing all tasks in
|
||||||
a multi-instance task"""
|
a multi-instance task"""
|
||||||
task.update_data(data)
|
task.update_data(data)
|
||||||
|
WorkflowService.post_process_form(task) # some properties may update the data store.
|
||||||
processor.complete_task(task)
|
processor.complete_task(task)
|
||||||
processor.do_engine_steps()
|
processor.do_engine_steps()
|
||||||
processor.save()
|
processor.save()
|
||||||
|
@ -26,7 +26,8 @@ class Task(object):
|
|||||||
PROP_EXTENSIONS_TITLE = "display_name"
|
PROP_EXTENSIONS_TITLE = "display_name"
|
||||||
|
|
||||||
|
|
||||||
# Autocomplete field
|
# Field Types
|
||||||
|
FIELD_TYPE_FILE = "file"
|
||||||
FIELD_TYPE_AUTO_COMPLETE = "autocomplete"
|
FIELD_TYPE_AUTO_COMPLETE = "autocomplete"
|
||||||
FIELD_PROP_AUTO_COMPLETE_MAX = "autocomplete_num" # Not used directly, passed in from the front end.
|
FIELD_PROP_AUTO_COMPLETE_MAX = "autocomplete_num" # Not used directly, passed in from the front end.
|
||||||
|
|
||||||
@ -59,6 +60,10 @@ class Task(object):
|
|||||||
FIELD_PROP_REPLEAT_TITLE = "repeat_title"
|
FIELD_PROP_REPLEAT_TITLE = "repeat_title"
|
||||||
FIELD_PROP_REPLEAT_BUTTON = "repeat_button_label"
|
FIELD_PROP_REPLEAT_BUTTON = "repeat_button_label"
|
||||||
|
|
||||||
|
# File specific field properties
|
||||||
|
FIELD_PROP_DOC_CODE = "doc_code" # to associate a file upload field with a doc code
|
||||||
|
FIELD_PROP_FILE_DATA = "file_data" # to associate a bit of data with a specific file upload file.
|
||||||
|
|
||||||
# Additional properties
|
# Additional properties
|
||||||
FIELD_PROP_ENUM_TYPE = "enum_type"
|
FIELD_PROP_ENUM_TYPE = "enum_type"
|
||||||
FIELD_PROP_TEXT_AREA_ROWS = "rows"
|
FIELD_PROP_TEXT_AREA_ROWS = "rows"
|
||||||
|
@ -90,7 +90,7 @@ class FileModel(db.Model):
|
|||||||
# it instead, hide it in the interface.
|
# it instead, hide it in the interface.
|
||||||
is_review = db.Column(db.Boolean, default=False, nullable=True)
|
is_review = db.Column(db.Boolean, default=False, nullable=True)
|
||||||
archived = db.Column(db.Boolean, default=False, nullable=False)
|
archived = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
tags = relationship("DataStoreModel", cascade="all,delete", backref="file")
|
data_stores = relationship("DataStoreModel", cascade="all,delete", backref="file")
|
||||||
|
|
||||||
class File(object):
|
class File(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -123,6 +123,11 @@ class File(object):
|
|||||||
else:
|
else:
|
||||||
instance.last_modified = None
|
instance.last_modified = None
|
||||||
instance.latest_version = None
|
instance.latest_version = None
|
||||||
|
|
||||||
|
instance.data_store = {}
|
||||||
|
for ds in model.data_stores:
|
||||||
|
instance.data_store[ds.key] = ds.value
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
@ -142,7 +147,7 @@ class FileSchema(Schema):
|
|||||||
fields = ["id", "name", "is_status", "is_reference", "content_type",
|
fields = ["id", "name", "is_status", "is_reference", "content_type",
|
||||||
"primary", "primary_process_id", "workflow_spec_id", "workflow_id",
|
"primary", "primary_process_id", "workflow_spec_id", "workflow_id",
|
||||||
"irb_doc_code", "last_modified", "latest_version", "type", "categories",
|
"irb_doc_code", "last_modified", "latest_version", "type", "categories",
|
||||||
"description", "category", "description", "download_name", "size"]
|
"description", "category", "download_name", "size", "data_store"]
|
||||||
|
|
||||||
unknown = INCLUDE
|
unknown = INCLUDE
|
||||||
type = EnumField(FileType)
|
type = EnumField(FileType)
|
||||||
|
@ -100,16 +100,6 @@ class FileService(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_workflow_file(workflow_id, irb_doc_code, name, content_type, binary_data):
|
def add_workflow_file(workflow_id, irb_doc_code, name, content_type, binary_data):
|
||||||
"""Create a new file and associate it with the workflow
|
|
||||||
Please note that the irb_doc_code MUST be a known file in the irb_documents.xslx reference document."""
|
|
||||||
if not FileService.is_allowed_document(irb_doc_code):
|
|
||||||
raise ApiError("invalid_form_field_key",
|
|
||||||
"When uploading files, the form field id must match a known document in the "
|
|
||||||
"irb_docunents.xslx reference file. This code is not found in that file '%s'" % irb_doc_code)
|
|
||||||
|
|
||||||
"""Assure this is unique to the workflow, task, and document code AND the Name
|
|
||||||
Because we will allow users to upload multiple files for the same form field
|
|
||||||
in some cases """
|
|
||||||
file_model = session.query(FileModel)\
|
file_model = session.query(FileModel)\
|
||||||
.filter(FileModel.workflow_id == workflow_id)\
|
.filter(FileModel.workflow_id == workflow_id)\
|
||||||
.filter(FileModel.name == name)\
|
.filter(FileModel.name == name)\
|
||||||
|
@ -22,6 +22,7 @@ from jinja2 import Template
|
|||||||
from crc import db, app
|
from crc import db, app
|
||||||
from crc.api.common import ApiError
|
from crc.api.common import ApiError
|
||||||
from crc.models.api_models import Task, MultiInstanceType, WorkflowApi
|
from crc.models.api_models import Task, MultiInstanceType, WorkflowApi
|
||||||
|
from crc.models.data_store import DataStoreModel
|
||||||
from crc.models.file import LookupDataModel, FileModel
|
from crc.models.file import LookupDataModel, FileModel
|
||||||
from crc.models.study import StudyModel
|
from crc.models.study import StudyModel
|
||||||
from crc.models.task_event import TaskEventModel
|
from crc.models.task_event import TaskEventModel
|
||||||
@ -242,6 +243,25 @@ class WorkflowService(object):
|
|||||||
f'The field {field.id} contains an unsupported '
|
f'The field {field.id} contains an unsupported '
|
||||||
f'property: {name}', task=task)
|
f'property: {name}', task=task)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def post_process_form(task):
|
||||||
|
"""Looks through the fields in a submitted form, acting on any properties."""
|
||||||
|
for field in task.task_spec.form.fields:
|
||||||
|
if field.has_property(Task.FIELD_PROP_DOC_CODE) and \
|
||||||
|
field.type == Task.FIELD_TYPE_FILE:
|
||||||
|
file_id = task.data[field.id]
|
||||||
|
file = db.session.query(FileModel).filter(FileModel.id == file_id).first()
|
||||||
|
doc_code = WorkflowService.evaluate_property(Task.FIELD_PROP_DOC_CODE, field, task)
|
||||||
|
file.irb_doc_code = doc_code
|
||||||
|
db.session.commit()
|
||||||
|
# Set the doc code on the file.
|
||||||
|
if field.has_property(Task.FIELD_PROP_FILE_DATA) and \
|
||||||
|
field.get_property(Task.FIELD_PROP_FILE_DATA) in task.data:
|
||||||
|
file_id = task.data[field.get_property(Task.FIELD_PROP_FILE_DATA)]
|
||||||
|
data_store = DataStoreModel(file_id=file_id, key=field.id, value=task.data[field.id])
|
||||||
|
db.session.add(data_store)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def evaluate_property(property_name, field, task):
|
def evaluate_property(property_name, field, task):
|
||||||
expression = field.get_property(property_name)
|
expression = field.get_property(property_name)
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1wrlvk8" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
|
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1wrlvk8" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.7.3">
|
||||||
<bpmn:process id="Finance" isExecutable="true">
|
<bpmn:process id="Finance" isExecutable="true">
|
||||||
<bpmn:startEvent id="StartEvent_1p6s47e">
|
<bpmn:startEvent id="StartEvent_1p6s47e">
|
||||||
<bpmn:outgoing>SequenceFlow_0ea9hvd</bpmn:outgoing>
|
<bpmn:outgoing>SequenceFlow_0ea9hvd</bpmn:outgoing>
|
||||||
</bpmn:startEvent>
|
</bpmn:startEvent>
|
||||||
<bpmn:endEvent id="EndEvent_14p904o">
|
<bpmn:endEvent id="EndEvent_14p904o">
|
||||||
<bpmn:incoming>SequenceFlow_1h0d349</bpmn:incoming>
|
<bpmn:incoming>Flow_0t55959</bpmn:incoming>
|
||||||
</bpmn:endEvent>
|
</bpmn:endEvent>
|
||||||
<bpmn:userTask id="Task_112migv" name="Upload Executed Non-Funded" camunda:formKey="FormKey_ExecutedNonFunded">
|
<bpmn:sequenceFlow id="SequenceFlow_0ea9hvd" sourceRef="StartEvent_1p6s47e" targetRef="Activity_0neioh9" />
|
||||||
|
<bpmn:userTask id="Activity_0neioh9" name="Upload Executed Non-Funded" camunda:formKey="FormKey_ExecutedNonFunded">
|
||||||
<bpmn:documentation>#### Non-Funded Executed Agreement
|
<bpmn:documentation>#### Non-Funded Executed Agreement
|
||||||
|
|
||||||
|
|
||||||
@ -15,40 +16,55 @@
|
|||||||
OGC will upload the Non-Funded Executed Agreement after it has been negotiated by OSP contract negotiator.</bpmn:documentation>
|
OGC will upload the Non-Funded Executed Agreement after it has been negotiated by OSP contract negotiator.</bpmn:documentation>
|
||||||
<bpmn:extensionElements>
|
<bpmn:extensionElements>
|
||||||
<camunda:formData>
|
<camunda:formData>
|
||||||
<camunda:formField id="UVACompl_PRCAppr" label="Non-Funded Executed Agreement" type="file">
|
<camunda:formField id="file_type" type="enum" defaultValue="FileType1">
|
||||||
|
<camunda:value id="FileType1" name="My First file type" />
|
||||||
|
<camunda:value id="FileType2" name="My second file type" />
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="Some_File" label="Upload File" type="file">
|
||||||
<camunda:properties>
|
<camunda:properties>
|
||||||
<camunda:property id="group" value="upload" />
|
<camunda:property id="group" value="PCRApproval" />
|
||||||
<camunda:property id="repeat" value="upload" />
|
<camunda:property id="doc_code" value="file_type" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="Language" label="Language" type="string" defaultValue="Engish">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="group" value="PCRApproval" />
|
||||||
|
<camunda:property id="file_data" value="Some_File" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
|
<camunda:formField id="Date" label="Version Date" type="date">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="group" value="PCRApproval" />
|
||||||
|
<camunda:property id="file_data" value="Some_File" />
|
||||||
</camunda:properties>
|
</camunda:properties>
|
||||||
</camunda:formField>
|
</camunda:formField>
|
||||||
</camunda:formData>
|
</camunda:formData>
|
||||||
</bpmn:extensionElements>
|
</bpmn:extensionElements>
|
||||||
<bpmn:incoming>SequenceFlow_0ea9hvd</bpmn:incoming>
|
<bpmn:incoming>SequenceFlow_0ea9hvd</bpmn:incoming>
|
||||||
<bpmn:outgoing>SequenceFlow_1h0d349</bpmn:outgoing>
|
<bpmn:outgoing>Flow_0t55959</bpmn:outgoing>
|
||||||
<bpmn:standardLoopCharacteristics />
|
<bpmn:standardLoopCharacteristics />
|
||||||
</bpmn:userTask>
|
</bpmn:userTask>
|
||||||
<bpmn:sequenceFlow id="SequenceFlow_0ea9hvd" sourceRef="StartEvent_1p6s47e" targetRef="Task_112migv" />
|
<bpmn:sequenceFlow id="Flow_0t55959" sourceRef="Activity_0neioh9" targetRef="EndEvent_14p904o" />
|
||||||
<bpmn:sequenceFlow id="SequenceFlow_1h0d349" sourceRef="Task_112migv" targetRef="EndEvent_14p904o" />
|
|
||||||
</bpmn:process>
|
</bpmn:process>
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Finance">
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Finance">
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_0ea9hvd_di" bpmnElement="SequenceFlow_0ea9hvd">
|
||||||
|
<di:waypoint x="148" y="117" />
|
||||||
|
<di:waypoint x="210" y="117" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0t55959_di" bpmnElement="Flow_0t55959">
|
||||||
|
<di:waypoint x="310" y="117" />
|
||||||
|
<di:waypoint x="392" y="117" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape id="StartEvent_1p6s47e_di" bpmnElement="StartEvent_1p6s47e">
|
<bpmndi:BPMNShape id="StartEvent_1p6s47e_di" bpmnElement="StartEvent_1p6s47e">
|
||||||
<dc:Bounds x="112" y="99" width="36" height="36" />
|
<dc:Bounds x="112" y="99" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="EndEvent_14p904o_di" bpmnElement="EndEvent_14p904o">
|
<bpmndi:BPMNShape id="EndEvent_14p904o_di" bpmnElement="EndEvent_14p904o">
|
||||||
<dc:Bounds x="682" y="99" width="36" height="36" />
|
<dc:Bounds x="392" y="99" width="36" height="36" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNShape id="UserTask_1peopdt_di" bpmnElement="Task_112migv">
|
<bpmndi:BPMNShape id="Activity_0neioh9_di" bpmnElement="Activity_0neioh9">
|
||||||
<dc:Bounds x="350" y="77" width="100" height="80" />
|
<dc:Bounds x="210" y="77" width="100" height="80" />
|
||||||
</bpmndi:BPMNShape>
|
</bpmndi:BPMNShape>
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_0ea9hvd_di" bpmnElement="SequenceFlow_0ea9hvd">
|
|
||||||
<di:waypoint x="148" y="117" />
|
|
||||||
<di:waypoint x="350" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="SequenceFlow_1h0d349_di" bpmnElement="SequenceFlow_1h0d349">
|
|
||||||
<di:waypoint x="450" y="117" />
|
|
||||||
<di:waypoint x="682" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
</bpmndi:BPMNPlane>
|
</bpmndi:BPMNPlane>
|
||||||
</bpmndi:BPMNDiagram>
|
</bpmndi:BPMNDiagram>
|
||||||
</bpmn:definitions>
|
</bpmn:definitions>
|
||||||
|
@ -8,6 +8,7 @@ from crc.models.file import FileModel, FileType, FileSchema, FileModelSchema
|
|||||||
from crc.models.workflow import WorkflowSpecModel
|
from crc.models.workflow import WorkflowSpecModel
|
||||||
from crc.services.file_service import FileService
|
from crc.services.file_service import FileService
|
||||||
from crc.services.workflow_processor import WorkflowProcessor
|
from crc.services.workflow_processor import WorkflowProcessor
|
||||||
|
from crc.models.data_store import DataStoreModel
|
||||||
from example_data import ExampleDataLoader
|
from example_data import ExampleDataLoader
|
||||||
|
|
||||||
|
|
||||||
@ -232,6 +233,17 @@ class TestFilesApi(BaseTest):
|
|||||||
self.assertEqual("text/xml; charset=utf-8", rv.content_type)
|
self.assertEqual("text/xml; charset=utf-8", rv.content_type)
|
||||||
self.assertTrue(rv.content_length > 1)
|
self.assertTrue(rv.content_length > 1)
|
||||||
|
|
||||||
|
def test_get_file_contains_data_store_elements(self):
|
||||||
|
self.load_example_data()
|
||||||
|
spec = session.query(WorkflowSpecModel).first()
|
||||||
|
file = session.query(FileModel).filter_by(workflow_spec_id=spec.id).first()
|
||||||
|
ds = DataStoreModel(key="my_key", value="my_value", file_id=file.id);
|
||||||
|
db.session.add(ds)
|
||||||
|
rv = self.app.get('/v1.0/file/%i' % file.id, headers=self.logged_in_headers())
|
||||||
|
self.assert_success(rv)
|
||||||
|
json_data = json.loads(rv.get_data(as_text=True))
|
||||||
|
self.assertEqual("my_value", json_data['data_store']['my_key'])
|
||||||
|
|
||||||
def test_get_files_for_form_field_returns_only_those_files(self):
|
def test_get_files_for_form_field_returns_only_those_files(self):
|
||||||
self.create_reference_document()
|
self.create_reference_document()
|
||||||
workflow = self.create_workflow('file_upload_form')
|
workflow = self.create_workflow('file_upload_form')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user