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:
Dan 2021-06-08 08:03:14 -04:00
parent ef7ee284b2
commit 59f605c3df
8 changed files with 83 additions and 34 deletions

View File

@ -57,7 +57,7 @@ def get_document_directory(study_id, workflow_id=None):
if file.irb_doc_code in doc_dict:
doc_code = doc_dict[file.irb_doc_code]
else:
doc_code = {'category1': "Unknown", 'category2': None, 'category3': None}
doc_code = {'category1': "Unknown", 'category2': '', 'category3': ''}
if workflow_id:
expand = file.workflow_id == int(workflow_id)
else:

View File

@ -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
a multi-instance task"""
task.update_data(data)
WorkflowService.post_process_form(task) # some properties may update the data store.
processor.complete_task(task)
processor.do_engine_steps()
processor.save()

View File

@ -26,7 +26,8 @@ class Task(object):
PROP_EXTENSIONS_TITLE = "display_name"
# Autocomplete field
# Field Types
FIELD_TYPE_FILE = "file"
FIELD_TYPE_AUTO_COMPLETE = "autocomplete"
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_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
FIELD_PROP_ENUM_TYPE = "enum_type"
FIELD_PROP_TEXT_AREA_ROWS = "rows"

View File

@ -90,7 +90,7 @@ class FileModel(db.Model):
# it instead, hide it in the interface.
is_review = db.Column(db.Boolean, default=False, nullable=True)
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):
@classmethod
@ -123,6 +123,11 @@ class File(object):
else:
instance.last_modified = None
instance.latest_version = None
instance.data_store = {}
for ds in model.data_stores:
instance.data_store[ds.key] = ds.value
return instance
@ -142,7 +147,7 @@ class FileSchema(Schema):
fields = ["id", "name", "is_status", "is_reference", "content_type",
"primary", "primary_process_id", "workflow_spec_id", "workflow_id",
"irb_doc_code", "last_modified", "latest_version", "type", "categories",
"description", "category", "description", "download_name", "size"]
"description", "category", "download_name", "size", "data_store"]
unknown = INCLUDE
type = EnumField(FileType)

View File

@ -100,16 +100,6 @@ class FileService(object):
@staticmethod
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)\
.filter(FileModel.workflow_id == workflow_id)\
.filter(FileModel.name == name)\

View File

@ -22,6 +22,7 @@ from jinja2 import Template
from crc import db, app
from crc.api.common import ApiError
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.study import StudyModel
from crc.models.task_event import TaskEventModel
@ -242,6 +243,25 @@ class WorkflowService(object):
f'The field {field.id} contains an unsupported '
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
def evaluate_property(property_name, field, task):
expression = field.get_property(property_name)

View File

@ -1,13 +1,14 @@
<?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:startEvent id="StartEvent_1p6s47e">
<bpmn:outgoing>SequenceFlow_0ea9hvd</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="EndEvent_14p904o">
<bpmn:incoming>SequenceFlow_1h0d349</bpmn:incoming>
<bpmn:incoming>Flow_0t55959</bpmn:incoming>
</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
@ -15,40 +16,55 @@
OGC will upload the Non-Funded Executed Agreement after it has been negotiated by OSP contract negotiator.</bpmn:documentation>
<bpmn:extensionElements>
<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:property id="group" value="upload" />
<camunda:property id="repeat" value="upload" />
<camunda:property id="group" value="PCRApproval" />
<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:formField>
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0ea9hvd</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1h0d349</bpmn:outgoing>
<bpmn:outgoing>Flow_0t55959</bpmn:outgoing>
<bpmn:standardLoopCharacteristics />
</bpmn:userTask>
<bpmn:sequenceFlow id="SequenceFlow_0ea9hvd" sourceRef="StartEvent_1p6s47e" targetRef="Task_112migv" />
<bpmn:sequenceFlow id="SequenceFlow_1h0d349" sourceRef="Task_112migv" targetRef="EndEvent_14p904o" />
<bpmn:sequenceFlow id="Flow_0t55959" sourceRef="Activity_0neioh9" targetRef="EndEvent_14p904o" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<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">
<dc:Bounds x="112" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<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 id="UserTask_1peopdt_di" bpmnElement="Task_112migv">
<dc:Bounds x="350" y="77" width="100" height="80" />
<bpmndi:BPMNShape id="Activity_0neioh9_di" bpmnElement="Activity_0neioh9">
<dc:Bounds x="210" y="77" width="100" height="80" />
</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:BPMNDiagram>
</bpmn:definitions>

View File

@ -8,6 +8,7 @@ from crc.models.file import FileModel, FileType, FileSchema, FileModelSchema
from crc.models.workflow import WorkflowSpecModel
from crc.services.file_service import FileService
from crc.services.workflow_processor import WorkflowProcessor
from crc.models.data_store import DataStoreModel
from example_data import ExampleDataLoader
@ -232,6 +233,17 @@ class TestFilesApi(BaseTest):
self.assertEqual("text/xml; charset=utf-8", rv.content_type)
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):
self.create_reference_document()
workflow = self.create_workflow('file_upload_form')