Merge branch 'dev' of https://github.com/sartography/cr-connect-workflow into dev
This commit is contained in:
commit
05ba28b67b
|
@ -979,7 +979,7 @@
|
|||
},
|
||||
"spiffworkflow": {
|
||||
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
||||
"ref": "66555b92ef1d8d9ce117b6f2ccf6aa248df9835f"
|
||||
"ref": "59d12e25e5313977b83e7d65b6deb65572dee71c"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
|
|
|
@ -63,6 +63,7 @@ PB_REQUIRED_DOCS_URL = environ.get('PB_REQUIRED_DOCS_URL', default=PB_BASE_URL +
|
|||
PB_STUDY_DETAILS_URL = environ.get('PB_STUDY_DETAILS_URL', default=PB_BASE_URL + "study?studyid=%i")
|
||||
PB_SPONSORS_URL = environ.get('PB_SPONSORS_URL', default=PB_BASE_URL + "sponsors?studyid=%i")
|
||||
PB_IRB_INFO_URL = environ.get('PB_IRB_INFO_URL', default=PB_BASE_URL + "current_irb_info/%i")
|
||||
PB_CHECK_STUDY_URL = environ.get('PB_CHECK_STUDY_URL', default=PB_BASE_URL + "check_study/%i")
|
||||
|
||||
# Ldap Configuration
|
||||
LDAP_URL = environ.get('LDAP_URL', default="ldap.virginia.edu").strip('/') # No trailing slash or http://
|
||||
|
|
|
@ -68,6 +68,7 @@ class FileDataModel(db.Model):
|
|||
date_created = db.Column(db.DateTime(timezone=True), server_default=func.now())
|
||||
file_model_id = db.Column(db.Integer, db.ForeignKey('file.id'))
|
||||
file_model = db.relationship("FileModel", foreign_keys=[file_model_id])
|
||||
user_uid = db.Column(db.String, db.ForeignKey('user.uid'), nullable=True)
|
||||
|
||||
|
||||
class FileModel(db.Model):
|
||||
|
@ -114,6 +115,7 @@ class File(object):
|
|||
instance.last_modified = data_model.date_created
|
||||
instance.latest_version = data_model.version
|
||||
instance.size = data_model.size
|
||||
instance.user_uid = data_model.user_uid
|
||||
else:
|
||||
instance.last_modified = None
|
||||
instance.latest_version = None
|
||||
|
@ -141,7 +143,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", "size", "data_store",
|
||||
"document"]
|
||||
"document", "user_uid"]
|
||||
unknown = INCLUDE
|
||||
type = EnumField(FileType)
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
from crc.scripts.script import Script
|
||||
from crc.api.common import ApiError
|
||||
from crc.services.protocol_builder import ProtocolBuilderService
|
||||
from crc.services.study_service import StudyService
|
||||
|
||||
|
||||
class CheckStudy(Script):
|
||||
|
||||
pb = ProtocolBuilderService()
|
||||
|
||||
def get_description(self):
|
||||
return """Returns the Check Study data for a Study"""
|
||||
|
||||
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
study = StudyService.get_study(study_id)
|
||||
if study:
|
||||
return {"DETAIL": "Passed validation.", "STATUS": "No Error"}
|
||||
else:
|
||||
raise ApiError.from_task(code='bad_study',
|
||||
message=f'No study for study_id {study_id}',
|
||||
task=task)
|
||||
|
||||
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
|
||||
check_study = self.pb.check_study(study_id)
|
||||
if check_study:
|
||||
return check_study
|
||||
else:
|
||||
raise ApiError.from_task(code='missing_check_study',
|
||||
message='There was a problem checking information for this study.',
|
||||
task=task)
|
|
@ -19,6 +19,7 @@ from crc.models.data_store import DataStoreModel
|
|||
from crc.models.file import FileType, FileDataModel, FileModel, LookupFileModel, LookupDataModel
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowModel, WorkflowSpecDependencyFile
|
||||
from crc.services.cache_service import cache
|
||||
from crc.services.user_service import UserService
|
||||
import re
|
||||
|
||||
|
||||
|
@ -168,10 +169,14 @@ class FileService(object):
|
|||
except XMLSyntaxError as xse:
|
||||
raise ApiError("invalid_xml", "Failed to parse xml: " + str(xse), file_name=file_model.name)
|
||||
|
||||
try:
|
||||
user_uid = UserService.current_user().uid
|
||||
except ApiError as ae:
|
||||
user_uid = None
|
||||
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.utcnow(),
|
||||
size=size
|
||||
size=size, user_uid=user_uid
|
||||
)
|
||||
session.add_all([file_model, new_file_data_model])
|
||||
session.commit()
|
||||
|
|
|
@ -15,6 +15,7 @@ class ProtocolBuilderService(object):
|
|||
STUDY_DETAILS_URL = app.config['PB_STUDY_DETAILS_URL']
|
||||
SPONSORS_URL = app.config['PB_SPONSORS_URL']
|
||||
IRB_INFO_URL = app.config['PB_IRB_INFO_URL']
|
||||
CHECK_STUDY_URL = app.config['PB_CHECK_STUDY_URL']
|
||||
|
||||
@staticmethod
|
||||
def is_enabled():
|
||||
|
@ -64,6 +65,10 @@ class ProtocolBuilderService(object):
|
|||
def get_sponsors(study_id) -> {}:
|
||||
return ProtocolBuilderService.__make_request(study_id, ProtocolBuilderService.SPONSORS_URL)
|
||||
|
||||
@staticmethod
|
||||
def check_study(study_id) -> {}:
|
||||
return ProtocolBuilderService.__make_request(study_id, ProtocolBuilderService.CHECK_STUDY_URL)
|
||||
|
||||
@staticmethod
|
||||
def __enabled_or_raise():
|
||||
if not ProtocolBuilderService.is_enabled():
|
||||
|
|
|
@ -7,7 +7,7 @@ import shlex
|
|||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from SpiffWorkflow import Task as SpiffTask, WorkflowException
|
||||
from SpiffWorkflow import Task as SpiffTask, WorkflowException, Task
|
||||
from SpiffWorkflow.bpmn.BpmnScriptEngine import BpmnScriptEngine
|
||||
from SpiffWorkflow.bpmn.parser.ValidationException import ValidationException
|
||||
from SpiffWorkflow.bpmn.serializer.BpmnSerializer import BpmnSerializer
|
||||
|
@ -30,6 +30,8 @@ from crc.services.file_service import FileService
|
|||
from crc import app
|
||||
from crc.services.user_service import UserService
|
||||
|
||||
from difflib import SequenceMatcher
|
||||
|
||||
class CustomBpmnScriptEngine(BpmnScriptEngine):
|
||||
"""This is a custom script processor that can be easily injected into Spiff Workflow.
|
||||
It will execute python code read in from the bpmn. It will also make any scripts in the
|
||||
|
@ -50,13 +52,11 @@ class CustomBpmnScriptEngine(BpmnScriptEngine):
|
|||
workflow_id = task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
|
||||
else:
|
||||
workflow_id = None
|
||||
|
||||
try:
|
||||
if task.workflow.data[WorkflowProcessor.VALIDATION_PROCESS_KEY]:
|
||||
augment_methods = Script.generate_augmented_validate_list(task, study_id, workflow_id)
|
||||
else:
|
||||
augment_methods = Script.generate_augmented_list(task, study_id, workflow_id)
|
||||
|
||||
super().execute(task, script, data, external_methods=augment_methods)
|
||||
except WorkflowException as e:
|
||||
raise e
|
||||
|
@ -337,6 +337,9 @@ class WorkflowProcessor(object):
|
|||
if bpmn_workflow.is_completed():
|
||||
return WorkflowStatus.complete
|
||||
user_tasks = bpmn_workflow.get_ready_user_tasks()
|
||||
waiting_tasks = bpmn_workflow.get_tasks(Task.WAITING)
|
||||
if len(waiting_tasks) > 0:
|
||||
return WorkflowStatus.waiting
|
||||
if len(user_tasks) > 0:
|
||||
return WorkflowStatus.user_input_required
|
||||
else:
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
"""add user_uid column to file_data table
|
||||
|
||||
Revision ID: 30e017a03948
|
||||
Revises: bbf064082623
|
||||
Create Date: 2021-07-06 10:39:04.661704
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '30e017a03948'
|
||||
down_revision = 'bbf064082623'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('file_data', sa.Column('user_uid', sa.String(), nullable=True))
|
||||
op.create_foreign_key(None, 'file_data', 'user', ['user_uid'], ['uid'])
|
||||
|
||||
|
||||
def downgrade():
|
||||
# op.drop_constraint('file_data_user_uid_fkey', 'file_data', type_='foreignkey')
|
||||
# op.execute("update file_data set user_uid = NULL WHERE user_uid IS NOT NULL")
|
||||
op.drop_column('file_data', 'user_uid')
|
|
@ -148,13 +148,6 @@ class BaseTest(unittest.TestCase):
|
|||
otherwise it depends on a small setup for running tests."""
|
||||
from example_data import ExampleDataLoader
|
||||
ExampleDataLoader.clean_db()
|
||||
if use_crc_data:
|
||||
ExampleDataLoader().load_all()
|
||||
elif use_rrt_data:
|
||||
ExampleDataLoader().load_rrt()
|
||||
else:
|
||||
ExampleDataLoader().load_test_data()
|
||||
|
||||
# If in production mode, only add the first user.
|
||||
if app.config['PRODUCTION']:
|
||||
session.add(UserModel(**self.users[0]))
|
||||
|
@ -162,6 +155,13 @@ class BaseTest(unittest.TestCase):
|
|||
for user_json in self.users:
|
||||
session.add(UserModel(**user_json))
|
||||
|
||||
if use_crc_data:
|
||||
ExampleDataLoader().load_all()
|
||||
elif use_rrt_data:
|
||||
ExampleDataLoader().load_rrt()
|
||||
else:
|
||||
ExampleDataLoader().load_test_data()
|
||||
|
||||
session.commit()
|
||||
for study_json in self.studies:
|
||||
study_model = StudyModel(**study_json)
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_3fd9241" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
|
||||
<bpmn:process id="Process_9d7b2c2" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_17nzcku</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_17nzcku" sourceRef="StartEvent_1" targetRef="Activity_GetCheckStudy" />
|
||||
<bpmn:scriptTask id="Activity_GetCheckStudy" name="Get Check Study">
|
||||
<bpmn:incoming>Flow_17nzcku</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_0oozrfg</bpmn:outgoing>
|
||||
<bpmn:script>check_study = check_study()</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_0oozrfg" sourceRef="Activity_GetCheckStudy" targetRef="Activity_DisplayCheckStudy" />
|
||||
<bpmn:manualTask id="Activity_DisplayCheckStudy" name="Display Check Study">
|
||||
<bpmn:documentation># Check Study
|
||||
<div><span>{{check_study}}</span></div></bpmn:documentation>
|
||||
<bpmn:incoming>Flow_0oozrfg</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_10sc31i</bpmn:outgoing>
|
||||
</bpmn:manualTask>
|
||||
<bpmn:endEvent id="Event_0embsc7">
|
||||
<bpmn:incoming>Flow_10sc31i</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_10sc31i" sourceRef="Activity_DisplayCheckStudy" targetRef="Event_0embsc7" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_9d7b2c2">
|
||||
<bpmndi:BPMNEdge id="Flow_10sc31i_di" bpmnElement="Flow_10sc31i">
|
||||
<di:waypoint x="530" y="177" />
|
||||
<di:waypoint x="592" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_0oozrfg_di" bpmnElement="Flow_0oozrfg">
|
||||
<di:waypoint x="370" y="177" />
|
||||
<di:waypoint x="430" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_17nzcku_di" bpmnElement="Flow_17nzcku">
|
||||
<di:waypoint x="215" y="177" />
|
||||
<di:waypoint x="270" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="179" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1f9d5ew_di" bpmnElement="Activity_GetCheckStudy">
|
||||
<dc:Bounds x="270" y="137" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_01vscea_di" bpmnElement="Activity_DisplayCheckStudy">
|
||||
<dc:Bounds x="430" y="137" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Event_0embsc7_di" bpmnElement="Event_0embsc7">
|
||||
<dc:Bounds x="592" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -0,0 +1,3 @@
|
|||
[
|
||||
{"DETAIL": "Passed validation.", "STATUS": "No Error"}
|
||||
]
|
|
@ -0,0 +1,57 @@
|
|||
<?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_8983dae" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
|
||||
<bpmn:process id="Process_2a4c7a5" isExecutable="true">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>Flow_13jyds8</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:sequenceFlow id="Flow_13jyds8" sourceRef="StartEvent_1" targetRef="Activity_GetData" />
|
||||
<bpmn:endEvent id="Event_03x966p">
|
||||
<bpmn:incoming>Flow_18kybym</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="Flow_18kybym" sourceRef="Activity_RunScript" targetRef="Event_03x966p" />
|
||||
<bpmn:scriptTask id="Activity_RunScript" name="Run Script">
|
||||
<bpmn:incoming>Flow_1jqzan6</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_18kybym</bpmn:outgoing>
|
||||
<bpmn:script>print(ham)</bpmn:script>
|
||||
</bpmn:scriptTask>
|
||||
<bpmn:sequenceFlow id="Flow_1jqzan6" sourceRef="Activity_GetData" targetRef="Activity_RunScript" />
|
||||
<bpmn:userTask id="Activity_GetData" name="Get Data" camunda:formKey="DataForm">
|
||||
<bpmn:extensionElements>
|
||||
<camunda:formData>
|
||||
<camunda:formField id="user" label="User" type="string" defaultValue="World" />
|
||||
<camunda:formField id="spam" label="Spam" type="boolean" defaultValue="False" />
|
||||
</camunda:formData>
|
||||
</bpmn:extensionElements>
|
||||
<bpmn:incoming>Flow_13jyds8</bpmn:incoming>
|
||||
<bpmn:outgoing>Flow_1jqzan6</bpmn:outgoing>
|
||||
</bpmn:userTask>
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_2a4c7a5">
|
||||
<bpmndi:BPMNEdge id="Flow_18kybym_di" bpmnElement="Flow_18kybym">
|
||||
<di:waypoint x="370" y="177" />
|
||||
<di:waypoint x="432" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_13jyds8_di" bpmnElement="Flow_13jyds8">
|
||||
<di:waypoint x="48" y="177" />
|
||||
<di:waypoint x="90" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNEdge id="Flow_1jqzan6_di" bpmnElement="Flow_1jqzan6">
|
||||
<di:waypoint x="190" y="177" />
|
||||
<di:waypoint x="270" y="177" />
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Event_03x966p_di" bpmnElement="Event_03x966p">
|
||||
<dc:Bounds x="432" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_1d9d2u8_di" bpmnElement="Activity_RunScript">
|
||||
<dc:Bounds x="270" y="137" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="12" y="159" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Activity_10ypwag_di" bpmnElement="Activity_GetData">
|
||||
<dc:Bounds x="90" y="137" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -126,6 +126,7 @@ class TestFilesApi(BaseTest):
|
|||
self.assertEqual(FileType.xlsx, file.type)
|
||||
self.assertTrue(file.is_reference)
|
||||
self.assertEqual("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.content_type)
|
||||
self.assertEqual('dhf8r', json_data['user_uid'])
|
||||
|
||||
def test_set_reference_file_bad_extension(self):
|
||||
file_name = DocumentService.DOCUMENT_LIST
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
from tests.base_test import BaseTest
|
||||
from crc import app
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class TestCheckStudy(BaseTest):
|
||||
|
||||
def test_check_study_script_validation(self):
|
||||
self.load_example_data()
|
||||
spec_model = self.load_test_spec('check_study_script')
|
||||
rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
|
||||
self.assertEqual([], rv.json)
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_check_study(self, mock_get):
|
||||
app.config['PB_ENABLED'] = True
|
||||
app.config['PB_ENABLED'] = True
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('check_study.json')
|
||||
workflow = self.create_workflow('check_study_script')
|
||||
workflow_api = self.get_workflow_api(workflow)
|
||||
task = workflow_api.next_task
|
||||
|
||||
self.assertIn('DETAIL', task.documentation)
|
||||
self.assertIn('STATUS', task.documentation)
|
|
@ -72,3 +72,13 @@ class TestProtocolBuilder(BaseTest):
|
|||
self.assertEqual('IRB Event 1', response[0]["IRBEVENT"])
|
||||
self.assertEqual('IRB Event 2', response[1]["IRBEVENT"])
|
||||
self.assertEqual('IRB Event 3', response[2]["IRBEVENT"])
|
||||
|
||||
@patch('crc.services.protocol_builder.requests.get')
|
||||
def test_check_study(self, mock_get):
|
||||
app.config['PB_ENABLED'] = True
|
||||
mock_get.return_value.ok = True
|
||||
mock_get.return_value.text = self.protocol_builder_response('check_study.json')
|
||||
response = ProtocolBuilderService.check_study(self.test_study_id)
|
||||
self.assertIsNotNone(response)
|
||||
self.assertIn('DETAIL', response[0].keys())
|
||||
self.assertIn('STATUS', response[0].keys())
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
from tests.base_test import BaseTest
|
||||
import json
|
||||
|
||||
|
||||
class TestNameErrorHint(BaseTest):
|
||||
|
||||
def test_name_error_hint(self):
|
||||
self.load_example_data()
|
||||
spec_model = self.load_test_spec('script_with_name_error')
|
||||
rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
|
||||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
self.assertIn('Did you mean \'[\'spam\'', json_data[0]['message'])
|
Loading…
Reference in New Issue