diff --git a/crc/api/study.py b/crc/api/study.py
index bbdcad01..593895a1 100644
--- a/crc/api/study.py
+++ b/crc/api/study.py
@@ -102,7 +102,7 @@ def user_studies():
if len(studies) == 0:
studies = StudyService().get_studies_for_user(user, include_invalid=True)
if len(studies) > 0:
- message = f"All studies associated with User: {user.display_name} failed study validation"
+ message = f"All studies associated with User: {user.uid} failed study validation"
raise ApiError(code="study_integrity_error", message=message)
results = StudySchema(many=True).dump(studies)
diff --git a/crc/models/email.py b/crc/models/email.py
index cf2bb543..96d3227b 100644
--- a/crc/models/email.py
+++ b/crc/models/email.py
@@ -26,5 +26,5 @@ class EmailModelSchema(ma.Schema):
class Meta:
model = EmailModel
- fields = ["id", "subject", "sender", "recipients", "cc", "bcc", "content", "content_html",
+ fields = ["id", "subject", "sender", "recipients", "cc", "bcc", "content",
"study_id", "timestamp", "workflow_spec_id"]
diff --git a/crc/models/task_log.py b/crc/models/task_log.py
new file mode 100644
index 00000000..0636c113
--- /dev/null
+++ b/crc/models/task_log.py
@@ -0,0 +1,23 @@
+from crc import db, ma
+from crc.models.study import StudyModel
+from crc.models.workflow import WorkflowModel
+from sqlalchemy import func
+
+
+class TaskLogModel(db.Model):
+ __tablename__ = 'task_log'
+ id = db.Column(db.Integer, primary_key=True)
+ level = db.Column(db.String)
+ code = db.Column(db.String)
+ message = db.Column(db.String)
+ study_id = db.Column(db.Integer, db.ForeignKey(StudyModel.id), nullable=False)
+ workflow_id = db.Column(db.Integer, db.ForeignKey(WorkflowModel.id), nullable=False)
+ task = db.Column(db.String)
+ timestamp = db.Column(db.DateTime(timezone=True), default=func.now())
+
+
+class TaskLogModelSchema(ma.Schema):
+
+ class Meta:
+ model = TaskLogModel
+ fields = ["id", "level", "code", "message", "study_id", "workflow_id", "timestamp"]
diff --git a/crc/scripts/email.py b/crc/scripts/email.py
index b732d2ea..40f1c744 100644
--- a/crc/scripts/email.py
+++ b/crc/scripts/email.py
@@ -1,5 +1,6 @@
import sys
import traceback
+import datetime
from crc import app, session
from crc.api.common import ApiError
@@ -45,7 +46,8 @@ email(subject="My Subject", recipients="user@example.com", attachments=['Study_A
subject = self.get_subject(kwargs['subject'])
recipients = self.get_email_addresses(kwargs['recipients'], study_id)
content, content_html = EmailService().get_rendered_content(task.task_spec.documentation, task.data)
- email_model = EmailModel(subject=subject,
+ email_model = EmailModel(id=1,
+ subject=subject,
recipients=recipients,
content=content,
content_html=content_html,
diff --git a/crc/scripts/get_email_data.py b/crc/scripts/get_email_data.py
index c3539823..4f787ddb 100644
--- a/crc/scripts/get_email_data.py
+++ b/crc/scripts/get_email_data.py
@@ -2,7 +2,9 @@ from crc.scripts.script import Script
from crc.api.common import ApiError
from crc import session
from crc.models.email import EmailModel, EmailModelSchema
-import json
+from crc.services.email_service import EmailService
+
+import datetime
class EmailData(Script):
@@ -12,11 +14,23 @@ class EmailData(Script):
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
if 'email_id' in kwargs or 'workflow_spec_id' in kwargs:
- return True
- else:
- return False
+ subject = 'My Test Email'
+ recipients = 'user@example.com'
+ content = "Hello"
+ content_html = "
Hello
"
+ email_model = EmailModel(subject=subject,
+ recipients=recipients,
+ content=content,
+ content_html=content_html,
+ timestamp=datetime.datetime.utcnow())
+ return EmailModelSchema(many=True).dump([email_model])
- def do_task(self, task, study_id, workflow_id, **kwargs):
+ else:
+ raise ApiError.from_task(code='missing_email_id',
+ message='You must include an email_id or workflow_spec_id with the get_email_data script.',
+ task=task)
+
+ def do_task(self, task, study_id, workflow_id, *args, **kwargs):
email_models = None
email_data = None
if 'email_id' in kwargs:
diff --git a/crc/scripts/get_localtime.py b/crc/scripts/get_localtime.py
index c3add55e..edc912e0 100644
--- a/crc/scripts/get_localtime.py
+++ b/crc/scripts/get_localtime.py
@@ -1,5 +1,3 @@
-import datetime
-
from crc.api.common import ApiError
from crc.scripts.script import Script
@@ -14,16 +12,21 @@ class GetLocaltime(Script):
Defaults to US/Eastern"""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
- if 'timestamp' in kwargs:
- return datetime.datetime.now()
+ if len(args) > 0 or 'timestamp' in kwargs:
+ return self.do_task(task, study_id, workflow_id, *args, **kwargs)
raise ApiError(code='missing_timestamp',
message='You must include a timestamp to convert.')
def do_task(self, task, study_id, workflow_id, *args, **kwargs):
- if 'timestamp' in kwargs:
- timestamp = kwargs['timestamp']
+ if len(args) > 0 or 'timestamp' in kwargs:
+ if 'timestamp' in kwargs:
+ timestamp = kwargs['timestamp']
+ else:
+ timestamp = args[0]
if 'timezone' in kwargs:
timezone = kwargs['timezone']
+ elif len(args) > 1:
+ timezone = args[1]
else:
timezone = 'US/Eastern'
parsed_timestamp = dateparser.parse(timestamp)
diff --git a/crc/scripts/get_logs.py b/crc/scripts/get_logs.py
new file mode 100644
index 00000000..53e3d667
--- /dev/null
+++ b/crc/scripts/get_logs.py
@@ -0,0 +1,38 @@
+from crc import session
+from crc.models.task_log import TaskLogModel, TaskLogModelSchema
+from crc.scripts.script import Script
+
+
+class GetLogsByWorkflow(Script):
+
+ def get_description(self):
+ return """Script to retrieve logs for the current workflow.
+ Accepts an optional `code` argument that is used to filter the DB query.
+ """
+
+ def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
+ log_model = TaskLogModel(level='info',
+ code='mocked_code',
+ message='This is my logging message',
+ study_id=study_id,
+ workflow_id=workflow_id,
+ task=task.get_name())
+ TaskLogModelSchema(many=True).dump([log_model])
+
+ def do_task(self, task, study_id, workflow_id, *args, **kwargs):
+ code = None
+ if 'code' in kwargs:
+ code = kwargs['code']
+ elif len(args) > 0:
+ code = args[0]
+ if code is not None:
+ log_models = session.query(TaskLogModel).\
+ filter(TaskLogModel.code == code).\
+ filter(TaskLogModel.workflow_id == workflow_id).\
+ all()
+ else:
+ log_models = session.query(TaskLogModel). \
+ filter(TaskLogModel.workflow_id == workflow_id). \
+ all()
+
+ return TaskLogModelSchema(many=True).dump(log_models)
diff --git a/crc/scripts/get_logs_for_study.py b/crc/scripts/get_logs_for_study.py
new file mode 100644
index 00000000..f02a25ed
--- /dev/null
+++ b/crc/scripts/get_logs_for_study.py
@@ -0,0 +1,38 @@
+from crc import session
+from crc.models.task_log import TaskLogModel, TaskLogModelSchema
+from crc.scripts.script import Script
+
+
+class GetLogsByWorkflow(Script):
+
+ def get_description(self):
+ return """Script to retrieve logs for the current study.
+ Accepts an optional `code` argument that is used to filter the DB query.
+ """
+
+ def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
+ log_model = TaskLogModel(level='info',
+ code='mocked_code',
+ message='This is my logging message',
+ study_id=study_id,
+ workflow_id=workflow_id,
+ task=task.get_name())
+ return TaskLogModelSchema(many=True).dump([log_model])
+
+ def do_task(self, task, study_id, workflow_id, *args, **kwargs):
+ code = None
+ if 'code' in kwargs:
+ code = kwargs['code']
+ elif len(args) > 0:
+ code = args[0]
+ if code is not None:
+ log_models = session.query(TaskLogModel).\
+ filter(TaskLogModel.code == code).\
+ filter(TaskLogModel.study_id == study_id).\
+ all()
+ else:
+ log_models = session.query(TaskLogModel). \
+ filter(TaskLogModel.study_id == study_id). \
+ all()
+
+ return TaskLogModelSchema(many=True).dump(log_models)
diff --git a/crc/scripts/log.py b/crc/scripts/log.py
new file mode 100644
index 00000000..3f33b603
--- /dev/null
+++ b/crc/scripts/log.py
@@ -0,0 +1,64 @@
+from crc import session
+from crc.api.common import ApiError
+from crc.models.task_log import TaskLogModel, TaskLogModelSchema
+from crc.scripts.script import Script
+
+
+class TaskLog(Script):
+
+ def get_description(self):
+ return """Script to log events in a Script Task.
+ Takes `level`, `code`, and `message` arguments.
+ Example:
+ log(level='info', code='missing_info', message='You must include the correct info!')
+
+ Level must be `debug`, `info`, `warning`, `error` or `critical`.
+ Code is a short string meant for searching the logs. By convention, it is lower case with underscores.
+ Message is a more descriptive string, including any info you want to log.
+ """
+
+ def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
+ if len(args) == 3 or ('level' in kwargs and 'code' in kwargs and 'message' in kwargs):
+ log_model = TaskLogModel(level='info',
+ code='mocked_code',
+ message='This is my logging message',
+ study_id=study_id,
+ workflow_id=workflow_id,
+ task=task.get_name())
+ return TaskLogModelSchema().dump(log_model)
+ else:
+ raise ApiError.from_task(code='missing_arguments',
+ message='You must include a level, code, and message to log.',
+ task=task)
+
+
+ def do_task(self, task, study_id, workflow_id, *args, **kwargs):
+ if len(args) == 3 or ('level' in kwargs and 'code' in kwargs and 'message' in kwargs):
+ if 'level' in kwargs:
+ level = kwargs['level']
+ else:
+ level = args[0]
+ if 'code' in kwargs:
+ code = kwargs['code']
+ else:
+ code = args[1]
+ if 'message' in kwargs:
+ message = kwargs['message']
+ else:
+ message = args[2]
+ task_name = task.get_name()
+ log_model = TaskLogModel(level=level,
+ code=code,
+ message=message,
+ study_id=study_id,
+ workflow_id=workflow_id,
+ task=task_name)
+ session.add(log_model)
+ session.commit()
+
+ return TaskLogModelSchema().dump(log_model)
+
+ else:
+ raise ApiError.from_task(code='missing_arguments',
+ message='You must include a level, code, and message to log.',
+ task=task)
diff --git a/crc/scripts/script.py b/crc/scripts/script.py
index edb3cbf5..556b2a74 100644
--- a/crc/scripts/script.py
+++ b/crc/scripts/script.py
@@ -13,12 +13,12 @@ class Script(object):
raise ApiError("invalid_script",
"This script does not supply a description.")
- def do_task(self, task, study_id, workflow_id, **kwargs):
+ def do_task(self, task, study_id, workflow_id, *args, **kwargs):
raise ApiError("invalid_script",
"This is an internal error. The script you are trying to execute '%s' " % self.__class__.__name__ +
"does not properly implement the do_task function.")
- def do_task_validate_only(self, task, study_id, workflow_id, **kwargs):
+ def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
raise ApiError("invalid_script",
"This is an internal error. The script you are trying to execute '%s' " % self.__class__.__name__ +
"does must provide a validate_only option that mimics the do_task, " +
diff --git a/crc/services/file_service.py b/crc/services/file_service.py
index aea6ba61..6a148b68 100644
--- a/crc/services/file_service.py
+++ b/crc/services/file_service.py
@@ -45,13 +45,15 @@ class FileService(object):
def add_workflow_spec_file(workflow_spec: WorkflowSpecModel,
name, content_type, binary_data, primary=False, is_status=False):
"""Create a new file and associate it with a workflow spec."""
- # Raise ApiError if the file already exists
- if session.query(FileModel)\
+ file_model = session.query(FileModel)\
.filter(FileModel.workflow_spec_id == workflow_spec.id)\
- .filter(FileModel.name == name).first():
+ .filter(FileModel.name == name).first()
- raise ApiError(code="Duplicate File",
- message='If you want to replace the file, use the update mechanism.')
+ if file_model:
+ if not file_model.archived:
+ # Raise ApiError if the file already exists and is not archived
+ raise ApiError(code="duplicate_file",
+ message='If you want to replace the file, use the update mechanism.')
else:
file_model = FileModel(
workflow_spec_id=workflow_spec.id,
@@ -60,7 +62,7 @@ class FileService(object):
is_status=is_status,
)
- return FileService.update_file(file_model, binary_data, content_type)
+ return FileService.update_file(file_model, binary_data, content_type)
diff --git a/crc/services/workflow_service.py b/crc/services/workflow_service.py
index 54150ea2..09409445 100755
--- a/crc/services/workflow_service.py
+++ b/crc/services/workflow_service.py
@@ -363,6 +363,10 @@ class WorkflowService(object):
def evaluate_property(property_name, field, task):
expression = field.get_property(property_name)
data = task.data
+ # If there's a field key with no initial value, give it one (None)
+ for field in task.task_spec.form.fields:
+ if field.id not in data:
+ data[field.id] = None
if field.has_property(Task.FIELD_PROP_REPEAT):
# Then you must evaluate the expression based on the data within the group, if that data exists.
# There may not be data available in the group, if no groups where added
diff --git a/deploy/requirements.txt b/deploy/requirements.txt
index 182048dd..95377c21 100644
--- a/deploy/requirements.txt
+++ b/deploy/requirements.txt
@@ -2,7 +2,7 @@ alabaster==0.7.12
alembic==1.4.3
aniso8601==8.0.0
attrs==20.3.0
-babel==2.9.0
+babel==2.9.1
bcrypt==3.2.0
beautifulsoup4==4.9.3
blinker==1.4
diff --git a/migrations/versions/a4f87f90cc64_add_log_table.py b/migrations/versions/a4f87f90cc64_add_log_table.py
new file mode 100644
index 00000000..ec981b66
--- /dev/null
+++ b/migrations/versions/a4f87f90cc64_add_log_table.py
@@ -0,0 +1,36 @@
+"""add log table
+
+Revision ID: a4f87f90cc64
+Revises: ba6df7e560a1
+Create Date: 2021-10-27 10:54:40.233325
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'a4f87f90cc64'
+down_revision = 'ba6df7e560a1'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ op.create_table('task_log',
+ sa.Column('id', sa.Integer(), nullable=False),
+ sa.Column('level', sa.String(), nullable=True),
+ sa.Column('code', sa.String(), nullable=True),
+ sa.Column('message', sa.String(), nullable=True),
+ sa.Column('study_id', sa.Integer(), nullable=False),
+ sa.Column('workflow_id', sa.Integer(), nullable=False),
+ sa.Column('task', sa.String(), nullable=True),
+ sa.Column('timestamp', sa.DateTime(timezone=True), nullable=True),
+ sa.ForeignKeyConstraint(['study_id'], ['study.id'], ),
+ sa.ForeignKeyConstraint(['workflow_id'], ['workflow.id'], ),
+ sa.PrimaryKeyConstraint('id')
+ )
+
+
+def downgrade():
+ op.drop_table('task_log')
diff --git a/tests/data/get_localtime/get_localtime.bpmn b/tests/data/get_localtime/get_localtime.bpmn
index 43a136d5..e8d91c63 100644
--- a/tests/data/get_localtime/get_localtime.bpmn
+++ b/tests/data/get_localtime/get_localtime.bpmn
@@ -1,58 +1,85 @@
-
+
Flow_0lnc9x0
-
-
-
+
+
+
Flow_0kgtoh1
-
- This is my email
- Flow_0lnc9x0
- Flow_0gtgzcf
- email_model = email(subject='My Email Subject', recipients='user@example.com')
-
-
+
timestamp = email_model.timestamp
localtime = get_localtime(str(timestamp))
Flow_0gtgzcf
Flow_0k1hbif
- timestamp=email_model.timestamp
-localtime = get_localtime(timestamp=timestamp)
+ if with_timestamp:
+ if with_timezone:
+ localtime_with = get_localtime(timestamp=timestamp, timezone=timezone)
+ localtime_without = get_localtime(timestamp, timezone)
+ else:
+ localtime_with = get_localtime(timestamp=timestamp)
+ localtime_without = get_localtime(timestamp)
+else:
+ localtime = get_localtime()
# Timestamp
{{ timestamp }}
+# Timezone
+{{ timezone }}
-# Localtime
-{{ localtime }}
+# Localtime With
+{{ localtime_with }}
+
+# Localtime Without
+{{ localtime_without }}
Flow_0k1hbif
Flow_0kgtoh1
+
+ This is my email
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Flow_0lnc9x0
+ Flow_0gtgzcf
+
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
+
+
+
+
@@ -60,15 +87,15 @@ localtime = get_localtime(timestamp=timestamp)
-
-
-
-
+
+
+
+
diff --git a/tests/data/get_logging/get_logging.bpmn b/tests/data/get_logging/get_logging.bpmn
new file mode 100644
index 00000000..d67463a2
--- /dev/null
+++ b/tests/data/get_logging/get_logging.bpmn
@@ -0,0 +1,96 @@
+
+
+
+
+ Flow_0d5wpav
+
+
+ Flow_0pc42yp
+ Flow_0n34cdi
+ log_model_info = log(level='info', code='test_code', message='You forgot to include the correct data.')
+log_model_debug = log(level='degug', code='debug_test_code', message='This is my debugging message')
+
+
+ # Logging Models Pre
+{{ logging_models_pre }}
+
+# Log Model
+{{ log_model }}
+
+# Logging Models All Post
+{{ logging_models_all_post }}
+
+
+# Logging Models Info Post
+{{ logging_models_info_post }}
+
+
+# Logging Models Debug Post
+{{ logging_models_debug_post }}
+ Flow_07j4f0v
+ Flow_016ui0e
+
+
+ Flow_016ui0e
+
+
+
+ Flow_0d5wpav
+ Flow_0pc42yp
+ logging_models_pre = get_logs()
+
+
+ Flow_0n34cdi
+ Flow_07j4f0v
+ logging_models_all_post = get_logs()
+logging_models_info_post = get_logs('test_code')
+logging_models_debug_post = get_logs('debug_test_code')
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/get_logging_for_study/get_logging_for_study.bpmn b/tests/data/get_logging_for_study/get_logging_for_study.bpmn
new file mode 100644
index 00000000..e3de15e6
--- /dev/null
+++ b/tests/data/get_logging_for_study/get_logging_for_study.bpmn
@@ -0,0 +1,90 @@
+
+
+
+
+ Flow_0bbqksl
+
+
+
+ # Hello
+You may manipulate this in a test, as you see fit
+ Flow_0bbqksl
+ Flow_0lh4lq8
+
+
+
+ Flow_0lh4lq8
+ Flow_10fc3fk
+ some_text = 'variable'
+log('info', 'some_code', 'Some longer message')
+log('info', 'some_other_code', 'Another really long message')
+log('debug', 'debug_code', f'This message has a { some_text }!')
+
+
+
+ Flow_10fc3fk
+ Flow_1dfqchi
+ workflow_logs = get_logs()
+study_logs = get_logs_for_study()
+
+
+
+ # Display Info
+
+## Workflow Logs
+{{ workflow_logs }}
+
+
+## Study Logs
+{{ study_logs }}
+ Flow_1dfqchi
+ Flow_0yxmlin
+
+
+ Flow_0yxmlin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/data/logging_task/logging_task.bpmn b/tests/data/logging_task/logging_task.bpmn
new file mode 100644
index 00000000..22193e0b
--- /dev/null
+++ b/tests/data/logging_task/logging_task.bpmn
@@ -0,0 +1,54 @@
+
+
+
+
+ Flow_1vjxvjd
+
+
+
+ Flow_1vjxvjd
+ Flow_1mw0dlv
+ log_model = log(level='info', code='test_code', message='You forgot to include the correct data.')
+
+
+
+ # Log Model
+{{ log_model }}
+
+ Flow_1mw0dlv
+ Flow_016ui0e
+
+
+ Flow_016ui0e
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/files/test_files_api.py b/tests/files/test_files_api.py
index 5ffd124b..43bd3d0c 100644
--- a/tests/files/test_files_api.py
+++ b/tests/files/test_files_api.py
@@ -5,7 +5,7 @@ import os
from tests.base_test import BaseTest
from crc import session, db, app
-from crc.models.file import FileModel, FileType, FileSchema, FileModelSchema
+from crc.models.file import FileModel, FileType, FileModelSchema, FileDataModel
from crc.models.workflow import WorkflowSpecModel
from crc.services.file_service import FileService
from crc.services.workflow_processor import WorkflowProcessor
@@ -13,6 +13,8 @@ from crc.models.data_store import DataStoreModel
from crc.services.document_service import DocumentService
from example_data import ExampleDataLoader
+from sqlalchemy import desc
+
class TestFilesApi(BaseTest):
@@ -376,3 +378,48 @@ class TestFilesApi(BaseTest):
json_data = json.loads(rv.get_data(as_text=True))
self.assertTrue(json_data['primary'])
self.assertIsNotNone(json_data['primary_process_id'])
+
+ def test_file_upload_with_previous_name(self):
+ self.load_example_data()
+ workflow_spec_model = session.query(WorkflowSpecModel).first()
+
+ # Add file
+ data = {'file': (io.BytesIO(b'asdf'), 'test_file.xlsx')}
+ rv = self.app.post('/v1.0/file?workflow_spec_id=%s' % workflow_spec_model.id,
+ data=data,
+ follow_redirects=True,
+ content_type='multipart/form-data',
+ headers=self.logged_in_headers())
+
+ self.assert_success(rv)
+ file_json = json.loads(rv.get_data(as_text=True))
+ file_id = file_json['id']
+
+ # Set file to archived
+ file_model = session.query(FileModel).filter_by(id=file_id).first()
+ file_model.archived = True
+ session.commit()
+
+ # Assert we have the correct file data and the file is archived
+ file_data_model = session.query(FileDataModel).filter(FileDataModel.file_model_id == file_model.id).first()
+ self.assertEqual(b'asdf', file_data_model.data)
+ file_model = session.query(FileModel).filter_by(id=file_model.id).first()
+ self.assertEqual(True, file_model.archived)
+
+ # Upload file with same name
+ data = {'file': (io.BytesIO(b'xyzpdq'), 'test_file.xlsx')}
+ rv = self.app.post('/v1.0/file?workflow_spec_id=%s' % workflow_spec_model.id,
+ data=data,
+ follow_redirects=True,
+ content_type='multipart/form-data',
+ headers=self.logged_in_headers())
+
+ self.assert_success(rv)
+ file_json = json.loads(rv.get_data(as_text=True))
+ file_id = file_json['id']
+
+ # Assert we have the correct file data and the file is *not* archived
+ file_data_model = session.query(FileDataModel).filter(FileDataModel.file_model_id == file_id).order_by(desc(FileDataModel.version)).first()
+ self.assertEqual(b'xyzpdq', file_data_model.data)
+ file_model = session.query(FileModel).filter_by(id=file_id).first()
+ self.assertEqual(False, file_model.archived)
diff --git a/tests/scripts/test_get_email_data.py b/tests/scripts/test_get_email_data.py
index b3b648d6..b9e45a37 100644
--- a/tests/scripts/test_get_email_data.py
+++ b/tests/scripts/test_get_email_data.py
@@ -6,6 +6,12 @@ from crc.services.email_service import EmailService
class TestGetEmailData(BaseTest):
+ def test_email_data_validation(self):
+ self.load_example_data()
+ spec_model = self.load_test_spec('get_email_data')
+ rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
+ self.assertEqual([], rv.json)
+
def test_get_email_data_by_email_id(self):
self.load_example_data()
workflow = self.create_workflow('get_email_data')
@@ -32,6 +38,8 @@ class TestGetEmailData(BaseTest):
self.assertEqual('My Email Subject', email_data[0]['subject'])
self.assertEqual('sender@example.com', email_data[0]['sender'])
self.assertEqual('[\'joe@example.com\']', email_data[0]['recipients'])
+ # Make sure we remove content_html from email_data
+ self.assertNotIn('content_html', email_data[0])
def test_get_email_data_by_workflow_spec_id(self):
self.load_example_data()
diff --git a/tests/scripts/test_get_localtime.py b/tests/scripts/test_get_localtime.py
index 4abac7ea..de54a982 100644
--- a/tests/scripts/test_get_localtime.py
+++ b/tests/scripts/test_get_localtime.py
@@ -1,6 +1,7 @@
from tests.base_test import BaseTest
from crc.scripts.get_localtime import GetLocaltime
import dateparser
+import datetime
class TestGetLocaltime(BaseTest):
@@ -8,11 +9,51 @@ class TestGetLocaltime(BaseTest):
def test_get_localtime(self):
self.load_example_data()
+ timestamp = datetime.datetime.utcnow()
workflow = self.create_workflow('get_localtime')
+
workflow_api = self.get_workflow_api(workflow)
task = workflow_api.next_task
- timestamp = task.data['timestamp']
- localtime = task.data['localtime']
+ workflow_api = self.complete_form(workflow, task, {'with_timestamp': True,
+ 'with_timezone': False,
+ 'timestamp': str(timestamp)})
+ task = workflow_api.next_task
- self.assertEqual(dateparser.parse(localtime), GetLocaltime().do_task(None, None, None, timestamp=timestamp))
+ # The workflow calls get_localtime twice, once with named arguments and once without
+ localtime_with = task.data['localtime_with']
+ localtime_without = task.data['localtime_without']
+
+ self.assertEqual(dateparser.parse(localtime_with), GetLocaltime().do_task(None, None, None, timestamp=str(timestamp)))
+ self.assertEqual(dateparser.parse(localtime_without), GetLocaltime().do_task(None, None, None, str(timestamp)))
+
+ def test_get_localtime_with_timezone(self):
+ self.load_example_data()
+
+ timestamp = datetime.datetime.utcnow()
+ workflow = self.create_workflow('get_localtime')
+
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+
+ workflow_api = self.complete_form(workflow, task, {'with_timestamp': True,
+ 'with_timezone': True,
+ 'timestamp': str(timestamp),
+ 'timezone': 'US/Eastern'})
+ task = workflow_api.next_task
+
+ # The workflow calls get_localtime twice, once with named arguments and once without
+ localtime_with = task.data['localtime_with']
+ localtime_without = task.data['localtime_without']
+
+ self.assertEqual(dateparser.parse(localtime_with), GetLocaltime().do_task(None, None, None, timestamp=str(timestamp), timezone='US/Eastern'))
+ self.assertEqual(dateparser.parse(localtime_without), GetLocaltime().do_task(None, None, None, str(timestamp), 'US/Eastern'))
+
+ def test_get_localtime_no_timestamp(self):
+ workflow = self.create_workflow('get_localtime')
+
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+
+ with self.assertRaises(AssertionError):
+ self.complete_form(workflow, task, {'with_timestamp': False, 'with_timezone': False})
diff --git a/tests/scripts/test_task_logging.py b/tests/scripts/test_task_logging.py
new file mode 100644
index 00000000..1e348165
--- /dev/null
+++ b/tests/scripts/test_task_logging.py
@@ -0,0 +1,99 @@
+from tests.base_test import BaseTest
+
+from crc import session
+from crc.models.api_models import Task
+from crc.models.task_log import TaskLogModel
+from crc.models.study import StudyModel
+from crc.scripts.log import TaskLog
+
+import types
+
+
+class TestTaskLogging(BaseTest):
+
+ def test_logging_validation(self):
+ self.load_example_data()
+ spec_model = self.load_test_spec('logging_task')
+ rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
+ self.assertEqual([], rv.json)
+
+ def test_add_log(self):
+ workflow = self.create_workflow('logging_task')
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+
+ log_id = task.data['log_model']['id']
+ log_model = session.query(TaskLogModel).filter(TaskLogModel.id == log_id).first()
+
+ self.assertEqual('test_code', log_model.code)
+ self.assertEqual('info', log_model.level)
+ self.assertEqual('Activity_LogEvent', log_model.task)
+
+ def test_get_logging_validation(self):
+ self.load_example_data()
+ spec_model = self.load_test_spec('get_logging')
+ rv = self.app.get('/v1.0/workflow-specification/%s/validate' % spec_model.id, headers=self.logged_in_headers())
+ self.assertEqual([], rv.json)
+
+ def test_get_logs(self):
+ workflow = self.create_workflow('get_logging')
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+
+ self.assertEqual(2, len(task.data['logging_models_all_post']))
+ self.assertEqual(1, len(task.data['logging_models_info_post']))
+ self.assertEqual(1, len(task.data['logging_models_debug_post']))
+ self.assertIn(task.data['logging_models_info_post'][0], task.data['logging_models_all_post'])
+ self.assertIn(task.data['logging_models_debug_post'][0], task.data['logging_models_all_post'])
+ self.assertEqual('test_code', task.data['logging_models_info_post'][0]['code'])
+ self.assertEqual('debug_test_code', task.data['logging_models_debug_post'][0]['code'])
+
+ def test_get_logs_for_study(self):
+ self.load_example_data()
+ study = session.query(StudyModel).first()
+
+ workflow = self.create_workflow('hello_world', study=study)
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+
+ task_model = Task(id=task.id,
+ name=task.name,
+ title=task.title,
+ type=task.type,
+ state=task.state,
+ lane=task.lane,
+ form=task.form,
+ documentation=task.documentation,
+ data=task.data,
+ multi_instance_type=task.multi_instance_type,
+ multi_instance_count=task.multi_instance_count,
+ multi_instance_index=task.multi_instance_index,
+ process_name=task.process_name,
+ properties=task.properties)
+
+ task_model.get_name = types.MethodType(lambda x: x.name, task_model)
+
+ TaskLog().do_task(task_model, study.id, workflow.id,
+ level='critical',
+ code='critical_code',
+ message='This is my critical message.')
+
+ TaskLog().do_task(task_model, study.id, workflow.id,
+ level='debug',
+ code='debug_code',
+ message='This is my debug message.')
+
+ # This workflow adds 3 logs
+ # some_text = 'variable'
+ # log('info', 'some_code', 'Some longer message')
+ # log('info', 'some_other_code', 'Another really long message')
+ # log('debug', 'debug_code', f'This message has a { some_text }!')
+ workflow = self.create_workflow('get_logging_for_study', study=study)
+ workflow_api = self.get_workflow_api(workflow)
+ task = workflow_api.next_task
+ workflow_api = self.complete_form(workflow, task, {})
+ task = workflow_api.next_task
+ workflow_logs = task.data['workflow_logs']
+ study_logs = task.data['study_logs']
+ self.assertEqual(3, len(workflow_logs))
+ self.assertEqual(5, len(study_logs))
diff --git a/tests/workflow/test_workflow_value_expression.py b/tests/workflow/test_workflow_value_expression.py
index 1be42451..af8f8426 100644
--- a/tests/workflow/test_workflow_value_expression.py
+++ b/tests/workflow/test_workflow_value_expression.py
@@ -3,6 +3,7 @@ from tests.base_test import BaseTest
class TestValueExpression(BaseTest):
+ # If there is no default value, a value of 'None' should be given.
def test_value_expression_no_default(self):
workflow = self.create_workflow('test_value_expression')
@@ -14,7 +15,7 @@ class TestValueExpression(BaseTest):
workflow_api = self.get_workflow_api(workflow)
second_task = workflow_api.next_task
self.assertEqual('', second_task.data['value_expression_value'])
- self.assertNotIn('color', second_task.data)
+ self.assertIn('color', second_task.data)