diff --git a/config/default.py b/config/default.py
index de512b38..464b4ef7 100644
--- a/config/default.py
+++ b/config/default.py
@@ -17,7 +17,10 @@ API_TOKEN = environ.get('API_TOKEN', default = 'af95596f327c9ecc007b60414fc84b61
NAME = "CR Connect Workflow"
DEFAULT_PORT = "5000"
FLASK_PORT = environ.get('PORT0') or environ.get('FLASK_PORT', default=DEFAULT_PORT)
-CORS_ALLOW_ORIGINS = re.split(r',\s*', environ.get('CORS_ALLOW_ORIGINS', default="localhost:4200, localhost:5002"))
+FRONTEND = "localhost:4200"
+BPMN = "localhost:5002"
+CORS_DEFAULT = f'{FRONTEND}, {BPMN}'
+CORS_ALLOW_ORIGINS = re.split(r',\s*', environ.get('CORS_ALLOW_ORIGINS', default=CORS_DEFAULT))
TESTING = environ.get('TESTING', default="false") == "true"
PRODUCTION = (environ.get('PRODUCTION', default="false") == "true")
TEST_UID = environ.get('TEST_UID', default="dhf8r")
@@ -50,7 +53,6 @@ SQLALCHEMY_DATABASE_URI = environ.get(
TOKEN_AUTH_TTL_HOURS = float(environ.get('TOKEN_AUTH_TTL_HOURS', default=24))
SECRET_KEY = environ.get('SECRET_KEY', default="Shhhh!!! This is secret! And better darn well not show up in prod.")
-FRONTEND_AUTH_CALLBACK = environ.get('FRONTEND_AUTH_CALLBACK', default="http://localhost:4200/session")
SWAGGER_AUTH_KEY = environ.get('SWAGGER_AUTH_KEY', default="SWAGGER")
# %s/%i placeholders expected for uva_id and study_id in various calls.
PB_ENABLED = environ.get('PB_ENABLED', default="false") == "true"
diff --git a/crc/api.yml b/crc/api.yml
index 2e36b93a..6501cebb 100644
--- a/crc/api.yml
+++ b/crc/api.yml
@@ -740,6 +740,41 @@ paths:
type: string
format: binary
example: ''
+ /file/{file_id}/download :
+ parameters :
+ - name : file_id
+ in : path
+ required : true
+ description : The id of the File requested
+ schema :
+ type : integer
+ - name : auth_token
+ in : query
+ required : true
+ description : User Auth Toeken
+ schema :
+ type : string
+ - name : version
+ in : query
+ required : false
+ description : The version of the file, or none for latest version
+ schema :
+ type : integer
+ get :
+ operationId : crc.api.file.get_file_data_link
+ summary : Returns only the file contents
+ security: []
+ tags :
+ - Files
+ responses :
+ '200' :
+ description : Returns the actual file
+ content :
+ application/octet-stream :
+ schema :
+ type : string
+ format : binary
+ example : ''
/file/{file_id}/data:
parameters:
- name: file_id
@@ -1574,6 +1609,7 @@ components:
standalone:
type: boolean
example: false
+ default: false
workflow_spec_category:
$ref: "#/components/schemas/WorkflowSpecCategory"
is_status:
diff --git a/crc/api/file.py b/crc/api/file.py
index b03b3778..911b2996 100644
--- a/crc/api/file.py
+++ b/crc/api/file.py
@@ -6,6 +6,7 @@ from flask import send_file
from crc import session
from crc.api.common import ApiError
+from crc.api.user import verify_token
from crc.models.api_models import DocumentDirectory, DocumentDirectorySchema
from crc.models.file import FileSchema, FileModel, File, FileModelSchema, FileDataModel, FileType
from crc.models.workflow import WorkflowSpecModel
@@ -182,6 +183,22 @@ def get_file_data(file_id, version=None):
)
+def get_file_data_link(file_id, auth_token, version=None):
+ if not verify_token(auth_token):
+ raise ApiError('not_authenticated', 'You need to include an authorization token in the URL with this')
+ file_data = FileService.get_file_data(file_id, version)
+ if file_data is None:
+ raise ApiError('no_such_file', 'The file id you provided does not exist')
+ return send_file(
+ io.BytesIO(file_data.data),
+ attachment_filename=file_data.file_model.name,
+ mimetype=file_data.file_model.content_type,
+ cache_timeout=-1, # Don't cache these files on the browser.
+ last_modified=file_data.date_created,
+ as_attachment = True
+ )
+
+
def get_file_info(file_id):
file_model = session.query(FileModel).filter_by(id=file_id).with_for_update().first()
if file_model is None:
diff --git a/crc/scripts/get_dashboard_url.py b/crc/scripts/get_dashboard_url.py
new file mode 100644
index 00000000..7730dab0
--- /dev/null
+++ b/crc/scripts/get_dashboard_url.py
@@ -0,0 +1,16 @@
+from crc.scripts.script import Script
+from crc import app
+
+
+class GetDashboardURL(Script):
+
+ def get_description(self):
+ """Get the URL for the main dashboard. This should be system instance aware.
+ I.e., dev, testing, production, etc."""
+
+ def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
+ self.do_task(task, study_id, workflow_id, *args, **kwargs)
+
+ def do_task(self, task, study_id, workflow_id, *args, **kwargs):
+ frontend = app.config['FRONTEND']
+ return f'https://{frontend}'
diff --git a/crc/services/study_service.py b/crc/services/study_service.py
index 4f3a8ca2..b01c42b8 100644
--- a/crc/services/study_service.py
+++ b/crc/services/study_service.py
@@ -1,7 +1,9 @@
+import urllib
from copy import copy
from datetime import datetime
from typing import List
+import flask
import requests
from SpiffWorkflow import WorkflowException
from SpiffWorkflow.exceptions import WorkflowTaskExecException
@@ -288,9 +290,19 @@ class StudyService(object):
doc_files = FileService.get_files_for_study(study_id=study_id, irb_doc_code=code)
doc['count'] = len(doc_files)
doc['files'] = []
+
+ # when we run tests - it doesn't look like the user is available
+ # so we return a bogus token
+ token = 'not_available'
+ if hasattr(flask.g,'user'):
+ token = flask.g.user.encode_auth_token()
for file in doc_files:
doc['files'].append({'file_id': file.id,
'name': file.name,
+ 'url': app.config['APPLICATION_ROOT']+
+ 'file/' + str(file.id) +
+ '/download?auth_token='+
+ urllib.parse.quote_plus(token),
'workflow_id': file.workflow_id})
# update the document status to match the status of the workflow it is in.
diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py
index 101929d4..ee37c688 100644
--- a/crc/services/workflow_service.py
+++ b/crc/services/workflow_service.py
@@ -22,7 +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.file import LookupDataModel
+from crc.models.file import LookupDataModel, FileModel
from crc.models.study import StudyModel
from crc.models.task_event import TaskEventModel
from crc.models.user import UserModel, UserModelSchema
@@ -739,10 +739,7 @@ class WorkflowService(object):
if hasattr(task.task_spec, 'form'):
for field in task.task_spec.form.fields:
- if field.has_property(Task.FIELD_PROP_READ_ONLY) and \
- field.get_property(Task.FIELD_PROP_READ_ONLY).lower().strip() == "true":
- continue # Don't add read-only data
- elif field.has_property(Task.FIELD_PROP_REPEAT):
+ if field.has_property(Task.FIELD_PROP_REPEAT):
group = field.get_property(Task.FIELD_PROP_REPEAT)
if group in latest_data:
data[group] = latest_data[group]
@@ -811,3 +808,12 @@ class WorkflowService(object):
def get_standalone_workflow_specs():
specs = db.session.query(WorkflowSpecModel).filter_by(standalone=True).all()
return specs
+
+ @staticmethod
+ def get_primary_workflow(workflow_spec_id):
+ # Returns the FileModel of the primary workflow for a workflow_spec
+ primary = None
+ file = db.session.query(FileModel).filter(FileModel.workflow_spec_id==workflow_spec_id, FileModel.primary==True).first()
+ if file:
+ primary = file
+ return primary
diff --git a/crc/static/jinja_extensions.py b/crc/static/jinja_extensions.py
new file mode 100644
index 00000000..fcbc7b55
--- /dev/null
+++ b/crc/static/jinja_extensions.py
@@ -0,0 +1,6 @@
+from crc.api.file import get_document_directory
+
+
+def render_files(study_id,irb_codes):
+ files = get_document_directory(study_id)
+ print(files)
\ No newline at end of file
diff --git a/tests/data/email_dashboard_url/email_dashboard_url.bpmn b/tests/data/email_dashboard_url/email_dashboard_url.bpmn
new file mode 100644
index 00000000..ce75e589
--- /dev/null
+++ b/tests/data/email_dashboard_url/email_dashboard_url.bpmn
@@ -0,0 +1,53 @@
+
+
+
+
+ Flow_0c51a4b
+
+
+
+
+ Flow_1rfvzi5
+
+
+
+ Flow_0c51a4b
+ Flow_1ker6ik
+ dashboard_url = get_dashboard_url()
+
+
+ <a href="{{dashboard_url}}">{{dashboard_url}}</a>
+ Flow_1ker6ik
+ Flow_1rfvzi5
+ email(subject='My Email Subject', recipients="test@example.com")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/file_data_store/file_data_store.bpmn b/tests/data/file_data_store/file_data_store.bpmn
index 9af77bb0..ddc9e987 100644
--- a/tests/data/file_data_store/file_data_store.bpmn
+++ b/tests/data/file_data_store/file_data_store.bpmn
@@ -18,6 +18,10 @@
fileid = documents['UVACompl_PRCAppr'].files[0]['file_id']
+fileurl = documents['UVACompl_PRCAppr'].files[0]['url']
+
+filename = documents['UVACompl_PRCAppr'].files[0]['name']
+
file_data_set(file_id=fileid,key='test',value='me')
diff --git a/tests/data/read_only_field/read_only_field.bpmn b/tests/data/read_only_field/read_only_field.bpmn
new file mode 100644
index 00000000..3620afdf
--- /dev/null
+++ b/tests/data/read_only_field/read_only_field.bpmn
@@ -0,0 +1,77 @@
+
+
+
+
+ Flow_0to8etb
+
+
+
+
+
+ Flow_0a95kns
+
+
+
+ Flow_0to8etb
+ Flow_04r75ca
+ string_value = 'asdf'
+
+
+
+
+
+
+
+
+
+
+
+
+ Flow_04r75ca
+ Flow_0g25v76
+
+
+ Read only is {{ read_only_field }}
+ Flow_0g25v76
+ Flow_0a95kns
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/test_file_datastore.py b/tests/test_file_datastore.py
index 2b95519e..1a10eea7 100644
--- a/tests/test_file_datastore.py
+++ b/tests/test_file_datastore.py
@@ -27,6 +27,8 @@ class TestFileDatastore(BaseTest):
processor = WorkflowProcessor(workflow)
processor.do_engine_steps()
task_data = processor.bpmn_workflow.last_task.data
+ self.assertTrue(str(task_data['fileid']) in task_data['fileurl'])
+ self.assertEqual(task_data['filename'],'anything.png')
self.assertEqual(task_data['output'], 'me')
self.assertEqual(task_data['output2'], 'nope')
diff --git a/tests/test_get_dashboard_url_script.py b/tests/test_get_dashboard_url_script.py
new file mode 100644
index 00000000..217942f6
--- /dev/null
+++ b/tests/test_get_dashboard_url_script.py
@@ -0,0 +1,17 @@
+from tests.base_test import BaseTest
+from crc import app, mail
+
+
+class TestGetDashboardURL(BaseTest):
+
+ def test_get_dashboard_url(self):
+ with mail.record_messages() as outbox:
+
+ dashboard_url = f'https://{app.config["FRONTEND"]}'
+ workflow = self.create_workflow('email_dashboard_url')
+ self.get_workflow_api(workflow)
+
+ self.assertEqual(1, len(outbox))
+ self.assertEqual('My Email Subject', outbox[0].subject)
+ self.assertEqual(['test@example.com'], outbox[0].recipients)
+ self.assertIn(dashboard_url, outbox[0].body)
diff --git a/tests/workflow/test_workflow_read_only_field.py b/tests/workflow/test_workflow_read_only_field.py
new file mode 100644
index 00000000..804650d8
--- /dev/null
+++ b/tests/workflow/test_workflow_read_only_field.py
@@ -0,0 +1,16 @@
+from tests.base_test import BaseTest
+
+
+class TestReadOnlyField(BaseTest):
+
+ def test_read_only(self):
+
+ workflow = self.create_workflow('read_only_field')
+ workflow_api = self.get_workflow_api(workflow)
+ first_task = workflow_api.next_task
+ read_only_field = first_task.data['read_only_field']
+ self.complete_form(workflow, first_task, {'read_only_field': read_only_field})
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+
+ self.assertEqual('Read only is asdf', task.documentation)
\ No newline at end of file
diff --git a/tests/workflow/test_workflow_service.py b/tests/workflow/test_workflow_service.py
index a4af4edc..cb4da9c2 100644
--- a/tests/workflow/test_workflow_service.py
+++ b/tests/workflow/test_workflow_service.py
@@ -10,6 +10,7 @@ from example_data import ExampleDataLoader
from crc import db
from crc.models.task_event import TaskEventModel
from crc.models.api_models import Task
+from crc.models.file import FileModel
from crc.api.common import ApiError
@@ -114,3 +115,12 @@ class TestWorkflowService(BaseTest):
result2 = WorkflowService.get_dot_value(path, {"a.b.c":"garbage"})
self.assertEqual("garbage", result2)
+
+ def test_get_primary_workflow(self):
+
+ workflow = self.create_workflow('hello_world')
+ workflow_spec_id = workflow.workflow_spec.id
+ primary_workflow = WorkflowService.get_primary_workflow(workflow_spec_id)
+ self.assertIsInstance(primary_workflow, FileModel)
+ self.assertEqual(workflow_spec_id, primary_workflow.workflow_spec_id)
+ self.assertEqual('hello_world.bpmn', primary_workflow.name)