Merge branch 'dev' into chore/view-as-382

This commit is contained in:
Dan Funk 2021-11-10 15:16:20 -05:00 committed by GitHub
commit 84f364ce37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 744 additions and 57 deletions

View File

@ -102,7 +102,7 @@ def user_studies():
if len(studies) == 0: if len(studies) == 0:
studies = StudyService().get_studies_for_user(user, include_invalid=True) studies = StudyService().get_studies_for_user(user, include_invalid=True)
if len(studies) > 0: 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) raise ApiError(code="study_integrity_error", message=message)
results = StudySchema(many=True).dump(studies) results = StudySchema(many=True).dump(studies)

View File

@ -26,5 +26,5 @@ class EmailModelSchema(ma.Schema):
class Meta: class Meta:
model = EmailModel 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"] "study_id", "timestamp", "workflow_spec_id"]

23
crc/models/task_log.py Normal file
View File

@ -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"]

View File

@ -1,5 +1,6 @@
import sys import sys
import traceback import traceback
import datetime
from crc import app, session from crc import app, session
from crc.api.common import ApiError 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']) subject = self.get_subject(kwargs['subject'])
recipients = self.get_email_addresses(kwargs['recipients'], study_id) recipients = self.get_email_addresses(kwargs['recipients'], study_id)
content, content_html = EmailService().get_rendered_content(task.task_spec.documentation, task.data) 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, recipients=recipients,
content=content, content=content,
content_html=content_html, content_html=content_html,

View File

@ -2,7 +2,9 @@ from crc.scripts.script import Script
from crc.api.common import ApiError from crc.api.common import ApiError
from crc import session from crc import session
from crc.models.email import EmailModel, EmailModelSchema from crc.models.email import EmailModel, EmailModelSchema
import json from crc.services.email_service import EmailService
import datetime
class EmailData(Script): class EmailData(Script):
@ -12,11 +14,23 @@ class EmailData(Script):
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs): def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
if 'email_id' in kwargs or 'workflow_spec_id' in kwargs: if 'email_id' in kwargs or 'workflow_spec_id' in kwargs:
return True subject = 'My Test Email'
else: recipients = 'user@example.com'
return False content = "Hello"
content_html = "<!DOCTYPE html><html><head></head><body><div><h2>Hello</h2></div></body></html>"
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_models = None
email_data = None email_data = None
if 'email_id' in kwargs: if 'email_id' in kwargs:

View File

@ -1,5 +1,3 @@
import datetime
from crc.api.common import ApiError from crc.api.common import ApiError
from crc.scripts.script import Script from crc.scripts.script import Script
@ -14,16 +12,21 @@ class GetLocaltime(Script):
Defaults to US/Eastern""" Defaults to US/Eastern"""
def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs): def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs):
if 'timestamp' in kwargs: if len(args) > 0 or 'timestamp' in kwargs:
return datetime.datetime.now() return self.do_task(task, study_id, workflow_id, *args, **kwargs)
raise ApiError(code='missing_timestamp', raise ApiError(code='missing_timestamp',
message='You must include a timestamp to convert.') message='You must include a timestamp to convert.')
def do_task(self, task, study_id, workflow_id, *args, **kwargs): def do_task(self, task, study_id, workflow_id, *args, **kwargs):
if 'timestamp' in kwargs: if len(args) > 0 or 'timestamp' in kwargs:
timestamp = kwargs['timestamp'] if 'timestamp' in kwargs:
timestamp = kwargs['timestamp']
else:
timestamp = args[0]
if 'timezone' in kwargs: if 'timezone' in kwargs:
timezone = kwargs['timezone'] timezone = kwargs['timezone']
elif len(args) > 1:
timezone = args[1]
else: else:
timezone = 'US/Eastern' timezone = 'US/Eastern'
parsed_timestamp = dateparser.parse(timestamp) parsed_timestamp = dateparser.parse(timestamp)

38
crc/scripts/get_logs.py Normal file
View File

@ -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)

View File

@ -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)

64
crc/scripts/log.py Normal file
View File

@ -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)

View File

@ -13,12 +13,12 @@ class Script(object):
raise ApiError("invalid_script", raise ApiError("invalid_script",
"This script does not supply a description.") "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", raise ApiError("invalid_script",
"This is an internal error. The script you are trying to execute '%s' " % self.__class__.__name__ + "This is an internal error. The script you are trying to execute '%s' " % self.__class__.__name__ +
"does not properly implement the do_task function.") "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", raise ApiError("invalid_script",
"This is an internal error. The script you are trying to execute '%s' " % self.__class__.__name__ + "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, " + "does must provide a validate_only option that mimics the do_task, " +

View File

@ -45,13 +45,15 @@ class FileService(object):
def add_workflow_spec_file(workflow_spec: WorkflowSpecModel, def add_workflow_spec_file(workflow_spec: WorkflowSpecModel,
name, content_type, binary_data, primary=False, is_status=False): name, content_type, binary_data, primary=False, is_status=False):
"""Create a new file and associate it with a workflow spec.""" """Create a new file and associate it with a workflow spec."""
# Raise ApiError if the file already exists file_model = session.query(FileModel)\
if session.query(FileModel)\
.filter(FileModel.workflow_spec_id == workflow_spec.id)\ .filter(FileModel.workflow_spec_id == workflow_spec.id)\
.filter(FileModel.name == name).first(): .filter(FileModel.name == name).first()
raise ApiError(code="Duplicate File", if file_model:
message='If you want to replace the file, use the update mechanism.') 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: else:
file_model = FileModel( file_model = FileModel(
workflow_spec_id=workflow_spec.id, workflow_spec_id=workflow_spec.id,
@ -60,7 +62,7 @@ class FileService(object):
is_status=is_status, is_status=is_status,
) )
return FileService.update_file(file_model, binary_data, content_type) return FileService.update_file(file_model, binary_data, content_type)

View File

@ -363,6 +363,10 @@ class WorkflowService(object):
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)
data = task.data 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): 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. # 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 # There may not be data available in the group, if no groups where added

View File

@ -2,7 +2,7 @@ alabaster==0.7.12
alembic==1.4.3 alembic==1.4.3
aniso8601==8.0.0 aniso8601==8.0.0
attrs==20.3.0 attrs==20.3.0
babel==2.9.0 babel==2.9.1
bcrypt==3.2.0 bcrypt==3.2.0
beautifulsoup4==4.9.3 beautifulsoup4==4.9.3
blinker==1.4 blinker==1.4

View File

@ -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')

View File

@ -1,58 +1,85 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0szq8v9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0"> <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_0szq8v9" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0">
<bpmn:process id="Process_1dxw783" name="Get Localtime" isExecutable="true"> <bpmn:process id="Process_1dxw783" name="Get Localtime" isExecutable="true">
<bpmn:startEvent id="StartEvent_1"> <bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0lnc9x0</bpmn:outgoing> <bpmn:outgoing>Flow_0lnc9x0</bpmn:outgoing>
</bpmn:startEvent> </bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0lnc9x0" sourceRef="StartEvent_1" targetRef="Activity_0aq21yg" /> <bpmn:sequenceFlow id="Flow_0lnc9x0" sourceRef="StartEvent_1" targetRef="Activity_GetData" />
<bpmn:sequenceFlow id="Flow_0gtgzcf" sourceRef="Activity_0aq21yg" targetRef="Activity_1by2ose" /> <bpmn:sequenceFlow id="Flow_0gtgzcf" sourceRef="Activity_GetData" targetRef="Activity_GetLocaltime" />
<bpmn:sequenceFlow id="Flow_0k1hbif" sourceRef="Activity_1by2ose" targetRef="Activity_0d5fjpa" /> <bpmn:sequenceFlow id="Flow_0k1hbif" sourceRef="Activity_GetLocaltime" targetRef="Activity_0d5fjpa" />
<bpmn:endEvent id="Event_1vxo45i"> <bpmn:endEvent id="Event_1vxo45i">
<bpmn:incoming>Flow_0kgtoh1</bpmn:incoming> <bpmn:incoming>Flow_0kgtoh1</bpmn:incoming>
</bpmn:endEvent> </bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0kgtoh1" sourceRef="Activity_0d5fjpa" targetRef="Event_1vxo45i" /> <bpmn:sequenceFlow id="Flow_0kgtoh1" sourceRef="Activity_0d5fjpa" targetRef="Event_1vxo45i" />
<bpmn:scriptTask id="Activity_0aq21yg" name="Send Email"> <bpmn:scriptTask id="Activity_GetLocaltime" name="Get Localtime">
<bpmn:documentation>This is my email</bpmn:documentation>
<bpmn:incoming>Flow_0lnc9x0</bpmn:incoming>
<bpmn:outgoing>Flow_0gtgzcf</bpmn:outgoing>
<bpmn:script>email_model = email(subject='My Email Subject', recipients='user@example.com')</bpmn:script>
</bpmn:scriptTask>
<bpmn:scriptTask id="Activity_1by2ose" name="Get Localtime">
<bpmn:documentation>timestamp = email_model.timestamp <bpmn:documentation>timestamp = email_model.timestamp
localtime = get_localtime(str(timestamp))</bpmn:documentation> localtime = get_localtime(str(timestamp))</bpmn:documentation>
<bpmn:incoming>Flow_0gtgzcf</bpmn:incoming> <bpmn:incoming>Flow_0gtgzcf</bpmn:incoming>
<bpmn:outgoing>Flow_0k1hbif</bpmn:outgoing> <bpmn:outgoing>Flow_0k1hbif</bpmn:outgoing>
<bpmn:script>timestamp=email_model.timestamp <bpmn:script>if with_timestamp:
localtime = get_localtime(timestamp=timestamp)</bpmn:script> 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()</bpmn:script>
</bpmn:scriptTask> </bpmn:scriptTask>
<bpmn:manualTask id="Activity_0d5fjpa" name="Display Times"> <bpmn:manualTask id="Activity_0d5fjpa" name="Display Times">
<bpmn:documentation># Timestamp <bpmn:documentation># Timestamp
{{ timestamp }} {{ timestamp }}
# Timezone
{{ timezone }}
# Localtime # Localtime With
{{ localtime }}</bpmn:documentation> {{ localtime_with }}
# Localtime Without
{{ localtime_without }}</bpmn:documentation>
<bpmn:incoming>Flow_0k1hbif</bpmn:incoming> <bpmn:incoming>Flow_0k1hbif</bpmn:incoming>
<bpmn:outgoing>Flow_0kgtoh1</bpmn:outgoing> <bpmn:outgoing>Flow_0kgtoh1</bpmn:outgoing>
</bpmn:manualTask> </bpmn:manualTask>
<bpmn:userTask id="Activity_GetData" name="Get Data" camunda:formKey="DataForm">
<bpmn:documentation>This is my email</bpmn:documentation>
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="with_timestamp" label="With Timestamp" type="boolean" defaultValue="False">
<camunda:validation>
<camunda:constraint name="required" config="True" />
</camunda:validation>
</camunda:formField>
<camunda:formField id="with_timezone" label="With Timezone" type="boolean" defaultValue="False">
<camunda:validation>
<camunda:constraint name="required" config="True" />
</camunda:validation>
</camunda:formField>
<camunda:formField id="timestamp" label="Timestamp" type="string" />
<camunda:formField id="timezone" label="Timezone" type="string" />
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0lnc9x0</bpmn:incoming>
<bpmn:outgoing>Flow_0gtgzcf</bpmn:outgoing>
</bpmn:userTask>
</bpmn:process> </bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1"> <bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1dxw783"> <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1dxw783">
<bpmndi:BPMNEdge id="Flow_0lnc9x0_di" bpmnElement="Flow_0lnc9x0"> <bpmndi:BPMNEdge id="Flow_0kgtoh1_di" bpmnElement="Flow_0kgtoh1">
<di:waypoint x="215" y="117" /> <di:waypoint x="690" y="117" />
<di:waypoint x="270" y="117" /> <di:waypoint x="752" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0gtgzcf_di" bpmnElement="Flow_0gtgzcf">
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0k1hbif_di" bpmnElement="Flow_0k1hbif"> <bpmndi:BPMNEdge id="Flow_0k1hbif_di" bpmnElement="Flow_0k1hbif">
<di:waypoint x="530" y="117" /> <di:waypoint x="530" y="117" />
<di:waypoint x="590" y="117" /> <di:waypoint x="590" y="117" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0kgtoh1_di" bpmnElement="Flow_0kgtoh1"> <bpmndi:BPMNEdge id="Flow_0gtgzcf_di" bpmnElement="Flow_0gtgzcf">
<di:waypoint x="690" y="117" /> <di:waypoint x="370" y="117" />
<di:waypoint x="752" y="117" /> <di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0lnc9x0_di" bpmnElement="Flow_0lnc9x0">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge> </bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1"> <bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" /> <dc:Bounds x="179" y="99" width="36" height="36" />
@ -60,15 +87,15 @@ localtime = get_localtime(timestamp=timestamp)</bpmn:script>
<bpmndi:BPMNShape id="Event_1vxo45i_di" bpmnElement="Event_1vxo45i"> <bpmndi:BPMNShape id="Event_1vxo45i_di" bpmnElement="Event_1vxo45i">
<dc:Bounds x="752" y="99" width="36" height="36" /> <dc:Bounds x="752" y="99" width="36" height="36" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_01qg6wo_di" bpmnElement="Activity_0aq21yg"> <bpmndi:BPMNShape id="Activity_0q4ycxr_di" bpmnElement="Activity_GetLocaltime">
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0q4ycxr_di" bpmnElement="Activity_1by2ose">
<dc:Bounds x="430" y="77" width="100" height="80" /> <dc:Bounds x="430" y="77" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_178gn50_di" bpmnElement="Activity_0d5fjpa"> <bpmndi:BPMNShape id="Activity_178gn50_di" bpmnElement="Activity_0d5fjpa">
<dc:Bounds x="590" y="77" width="100" height="80" /> <dc:Bounds x="590" y="77" width="100" height="80" />
</bpmndi:BPMNShape> </bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0jhiwc1_di" bpmnElement="Activity_GetData">
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane> </bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram> </bpmndi:BPMNDiagram>
</bpmn:definitions> </bpmn:definitions>

View File

@ -0,0 +1,96 @@
<?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_e3059e6" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0">
<bpmn:process id="Process_LoggingTask" name="Logging Task" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0d5wpav</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:scriptTask id="Activity_LogEvent" name="Log Event">
<bpmn:incoming>Flow_0pc42yp</bpmn:incoming>
<bpmn:outgoing>Flow_0n34cdi</bpmn:outgoing>
<bpmn:script>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')</bpmn:script>
</bpmn:scriptTask>
<bpmn:manualTask id="Activity_DisplayLog" name="DisplayLog">
<bpmn:documentation># 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 }}</bpmn:documentation>
<bpmn:incoming>Flow_07j4f0v</bpmn:incoming>
<bpmn:outgoing>Flow_016ui0e</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:endEvent id="Event_06g3ojm">
<bpmn:incoming>Flow_016ui0e</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_016ui0e" sourceRef="Activity_DisplayLog" targetRef="Event_06g3ojm" />
<bpmn:scriptTask id="Activity_GetLoggingPre" name="Get Logging Pre">
<bpmn:incoming>Flow_0d5wpav</bpmn:incoming>
<bpmn:outgoing>Flow_0pc42yp</bpmn:outgoing>
<bpmn:script>logging_models_pre = get_logs()</bpmn:script>
</bpmn:scriptTask>
<bpmn:scriptTask id="Activity_GetLoggingPost" name="Get Logging Post">
<bpmn:incoming>Flow_0n34cdi</bpmn:incoming>
<bpmn:outgoing>Flow_07j4f0v</bpmn:outgoing>
<bpmn:script>logging_models_all_post = get_logs()
logging_models_info_post = get_logs('test_code')
logging_models_debug_post = get_logs('debug_test_code')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_0d5wpav" sourceRef="StartEvent_1" targetRef="Activity_GetLoggingPre" />
<bpmn:sequenceFlow id="Flow_0pc42yp" sourceRef="Activity_GetLoggingPre" targetRef="Activity_LogEvent" />
<bpmn:sequenceFlow id="Flow_0n34cdi" sourceRef="Activity_LogEvent" targetRef="Activity_GetLoggingPost" />
<bpmn:sequenceFlow id="Flow_07j4f0v" sourceRef="Activity_GetLoggingPost" targetRef="Activity_DisplayLog" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_LoggingTask">
<bpmndi:BPMNEdge id="Flow_07j4f0v_di" bpmnElement="Flow_07j4f0v">
<di:waypoint x="650" y="117" />
<di:waypoint x="710" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0n34cdi_di" bpmnElement="Flow_0n34cdi">
<di:waypoint x="490" y="117" />
<di:waypoint x="550" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0pc42yp_di" bpmnElement="Flow_0pc42yp">
<di:waypoint x="330" y="117" />
<di:waypoint x="390" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0d5wpav_di" bpmnElement="Flow_0d5wpav">
<di:waypoint x="188" y="117" />
<di:waypoint x="230" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_016ui0e_di" bpmnElement="Flow_016ui0e">
<di:waypoint x="810" y="117" />
<di:waypoint x="872" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_08z1eq4_di" bpmnElement="Activity_LogEvent">
<dc:Bounds x="390" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0amiq7i_di" bpmnElement="Activity_DisplayLog">
<dc:Bounds x="710" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_06g3ojm_di" bpmnElement="Event_06g3ojm">
<dc:Bounds x="872" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_07mg1d2_di" bpmnElement="Activity_GetLoggingPre">
<dc:Bounds x="230" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_167quv8_di" bpmnElement="Activity_GetLoggingPost">
<dc:Bounds x="550" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,90 @@
<?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_0tt6u6r" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.2.0">
<bpmn:process id="Process_0jo811u" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_0bbqksl</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_0bbqksl" sourceRef="StartEvent_1" targetRef="Activity_ManualTask" />
<bpmn:manualTask id="Activity_ManualTask" name="Manual Task">
<bpmn:documentation># Hello
You may manipulate this in a test, as you see fit</bpmn:documentation>
<bpmn:incoming>Flow_0bbqksl</bpmn:incoming>
<bpmn:outgoing>Flow_0lh4lq8</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:sequenceFlow id="Flow_0lh4lq8" sourceRef="Activity_ManualTask" targetRef="Activity_AddLogs" />
<bpmn:scriptTask id="Activity_AddLogs" name="Add Logs">
<bpmn:incoming>Flow_0lh4lq8</bpmn:incoming>
<bpmn:outgoing>Flow_10fc3fk</bpmn:outgoing>
<bpmn:script>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 }!')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_10fc3fk" sourceRef="Activity_AddLogs" targetRef="Activity_GetLogs" />
<bpmn:scriptTask id="Activity_GetLogs" name="Get Logs">
<bpmn:incoming>Flow_10fc3fk</bpmn:incoming>
<bpmn:outgoing>Flow_1dfqchi</bpmn:outgoing>
<bpmn:script>workflow_logs = get_logs()
study_logs = get_logs_for_study()</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_1dfqchi" sourceRef="Activity_GetLogs" targetRef="Activity_DisplayInfo" />
<bpmn:manualTask id="Activity_DisplayInfo" name="Display Info">
<bpmn:documentation># Display Info
## Workflow Logs
{{ workflow_logs }}
## Study Logs
{{ study_logs }}</bpmn:documentation>
<bpmn:incoming>Flow_1dfqchi</bpmn:incoming>
<bpmn:outgoing>Flow_0yxmlin</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:endEvent id="Event_1dg0buo">
<bpmn:incoming>Flow_0yxmlin</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0yxmlin" sourceRef="Activity_DisplayInfo" targetRef="Event_1dg0buo" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0jo811u">
<bpmndi:BPMNEdge id="Flow_0bbqksl_di" bpmnElement="Flow_0bbqksl">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0lh4lq8_di" bpmnElement="Flow_0lh4lq8">
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_10fc3fk_di" bpmnElement="Flow_10fc3fk">
<di:waypoint x="530" y="117" />
<di:waypoint x="590" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1dfqchi_di" bpmnElement="Flow_1dfqchi">
<di:waypoint x="690" y="117" />
<di:waypoint x="750" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0yxmlin_di" bpmnElement="Flow_0yxmlin">
<di:waypoint x="850" y="117" />
<di:waypoint x="912" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0gphpwt_di" bpmnElement="Activity_ManualTask">
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0yb5wv4_di" bpmnElement="Activity_AddLogs">
<dc:Bounds x="430" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1d90rzn_di" bpmnElement="Activity_GetLogs">
<dc:Bounds x="590" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_016wmmv_di" bpmnElement="Activity_DisplayInfo">
<dc:Bounds x="750" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1dg0buo_di" bpmnElement="Event_1dg0buo">
<dc:Bounds x="912" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,54 @@
<?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_e3059e6" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.0-dev">
<bpmn:process id="Process_LoggingTask" name="Logging Task" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1vjxvjd</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1vjxvjd" sourceRef="StartEvent_1" targetRef="Activity_LogEvent" />
<bpmn:scriptTask id="Activity_LogEvent" name="Log Event">
<bpmn:incoming>Flow_1vjxvjd</bpmn:incoming>
<bpmn:outgoing>Flow_1mw0dlv</bpmn:outgoing>
<bpmn:script>log_model = log(level='info', code='test_code', message='You forgot to include the correct data.')</bpmn:script>
</bpmn:scriptTask>
<bpmn:sequenceFlow id="Flow_1mw0dlv" sourceRef="Activity_LogEvent" targetRef="Activity_DisplayLog" />
<bpmn:manualTask id="Activity_DisplayLog" name="DisplayLog">
<bpmn:documentation># Log Model
{{ log_model }}
</bpmn:documentation>
<bpmn:incoming>Flow_1mw0dlv</bpmn:incoming>
<bpmn:outgoing>Flow_016ui0e</bpmn:outgoing>
</bpmn:manualTask>
<bpmn:endEvent id="Event_06g3ojm">
<bpmn:incoming>Flow_016ui0e</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_016ui0e" sourceRef="Activity_DisplayLog" targetRef="Event_06g3ojm" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_LoggingTask">
<bpmndi:BPMNEdge id="Flow_016ui0e_di" bpmnElement="Flow_016ui0e">
<di:waypoint x="530" y="177" />
<di:waypoint x="592" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1mw0dlv_di" bpmnElement="Flow_1mw0dlv">
<di:waypoint x="370" y="177" />
<di:waypoint x="430" y="177" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1vjxvjd_di" bpmnElement="Flow_1vjxvjd">
<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_08z1eq4_di" bpmnElement="Activity_LogEvent">
<dc:Bounds x="270" y="137" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0amiq7i_di" bpmnElement="Activity_DisplayLog">
<dc:Bounds x="430" y="137" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_06g3ojm_di" bpmnElement="Event_06g3ojm">
<dc:Bounds x="592" y="159" width="36" height="36" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -5,7 +5,7 @@ import os
from tests.base_test import BaseTest from tests.base_test import BaseTest
from crc import session, db, app 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.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
@ -13,6 +13,8 @@ from crc.models.data_store import DataStoreModel
from crc.services.document_service import DocumentService from crc.services.document_service import DocumentService
from example_data import ExampleDataLoader from example_data import ExampleDataLoader
from sqlalchemy import desc
class TestFilesApi(BaseTest): class TestFilesApi(BaseTest):
@ -376,3 +378,48 @@ class TestFilesApi(BaseTest):
json_data = json.loads(rv.get_data(as_text=True)) json_data = json.loads(rv.get_data(as_text=True))
self.assertTrue(json_data['primary']) self.assertTrue(json_data['primary'])
self.assertIsNotNone(json_data['primary_process_id']) 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)

View File

@ -6,6 +6,12 @@ from crc.services.email_service import EmailService
class TestGetEmailData(BaseTest): 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): def test_get_email_data_by_email_id(self):
self.load_example_data() self.load_example_data()
workflow = self.create_workflow('get_email_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('My Email Subject', email_data[0]['subject'])
self.assertEqual('sender@example.com', email_data[0]['sender']) self.assertEqual('sender@example.com', email_data[0]['sender'])
self.assertEqual('[\'joe@example.com\']', email_data[0]['recipients']) 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): def test_get_email_data_by_workflow_spec_id(self):
self.load_example_data() self.load_example_data()

View File

@ -1,6 +1,7 @@
from tests.base_test import BaseTest from tests.base_test import BaseTest
from crc.scripts.get_localtime import GetLocaltime from crc.scripts.get_localtime import GetLocaltime
import dateparser import dateparser
import datetime
class TestGetLocaltime(BaseTest): class TestGetLocaltime(BaseTest):
@ -8,11 +9,51 @@ class TestGetLocaltime(BaseTest):
def test_get_localtime(self): def test_get_localtime(self):
self.load_example_data() self.load_example_data()
timestamp = datetime.datetime.utcnow()
workflow = self.create_workflow('get_localtime') workflow = self.create_workflow('get_localtime')
workflow_api = self.get_workflow_api(workflow) workflow_api = self.get_workflow_api(workflow)
task = workflow_api.next_task task = workflow_api.next_task
timestamp = task.data['timestamp'] workflow_api = self.complete_form(workflow, task, {'with_timestamp': True,
localtime = task.data['localtime'] '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})

View File

@ -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))

View File

@ -3,6 +3,7 @@ from tests.base_test import BaseTest
class TestValueExpression(BaseTest): class TestValueExpression(BaseTest):
# If there is no default value, a value of 'None' should be given.
def test_value_expression_no_default(self): def test_value_expression_no_default(self):
workflow = self.create_workflow('test_value_expression') workflow = self.create_workflow('test_value_expression')
@ -14,7 +15,7 @@ class TestValueExpression(BaseTest):
workflow_api = self.get_workflow_api(workflow) workflow_api = self.get_workflow_api(workflow)
second_task = workflow_api.next_task second_task = workflow_api.next_task
self.assertEqual('', second_task.data['value_expression_value']) self.assertEqual('', second_task.data['value_expression_value'])
self.assertNotIn('color', second_task.data) self.assertIn('color', second_task.data)