Merge branch 'rrt/dev' into feature/mail-system
This commit is contained in:
commit
16ca4fa2c0
|
@ -468,7 +468,7 @@ paths:
|
|||
$ref: "#/components/schemas/File"
|
||||
delete:
|
||||
operationId: crc.api.file.delete_file
|
||||
summary: Removes an existing file
|
||||
summary: Removes an existing file. In the event the file can not be deleted, it is marked as "archived" in the database and is no longer returned unless specifically requested by id.
|
||||
tags:
|
||||
- Files
|
||||
responses:
|
||||
|
|
|
@ -3,28 +3,21 @@ import pickle
|
|||
from base64 import b64decode
|
||||
from datetime import datetime
|
||||
|
||||
from SpiffWorkflow import Workflow
|
||||
from SpiffWorkflow.serializer.dict import DictionarySerializer
|
||||
from SpiffWorkflow.serializer.json import JSONSerializer
|
||||
from flask import g
|
||||
|
||||
from crc import app, db, session
|
||||
|
||||
from crc.api.common import ApiError, ApiErrorSchema
|
||||
from crc import db, session
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.approval import Approval, ApprovalModel, ApprovalSchema, ApprovalStatus
|
||||
from crc.models.ldap import LdapSchema
|
||||
from crc.models.study import Study
|
||||
from crc.models.workflow import WorkflowModel
|
||||
from crc.services.approval_service import ApprovalService
|
||||
from crc.services.ldap_service import LdapService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
|
||||
|
||||
def get_approvals(everything=False):
|
||||
if everything:
|
||||
approvals = ApprovalService.get_all_approvals()
|
||||
approvals = ApprovalService.get_all_approvals(include_cancelled=True)
|
||||
else:
|
||||
approvals = ApprovalService.get_approvals_per_user(g.user.uid)
|
||||
approvals = ApprovalService.get_approvals_per_user(g.user.uid, include_cancelled=False)
|
||||
results = ApprovalSchema(many=True).dump(approvals)
|
||||
return results
|
||||
|
||||
|
@ -38,12 +31,11 @@ def get_approvals_for_study(study_id=None):
|
|||
|
||||
# ----- Being decent into madness ---- #
|
||||
def get_csv():
|
||||
"""A huge bit of a one-off for RRT, but 3 weeks of midnight work can convince a
|
||||
"""A damn lie, it's a json file. A huge bit of a one-off for RRT, but 3 weeks of midnight work can convince a
|
||||
man to do just about anything"""
|
||||
approvals = ApprovalService.get_all_approvals(ignore_cancelled=True)
|
||||
approvals = ApprovalService.get_all_approvals(include_cancelled=False)
|
||||
output = []
|
||||
errors = []
|
||||
ldapService = LdapService()
|
||||
for approval in approvals:
|
||||
try:
|
||||
if approval.status != ApprovalStatus.APPROVED.value:
|
||||
|
@ -56,24 +48,33 @@ def get_csv():
|
|||
last_task = find_task(data['last_task']['__uuid__'], data['task_tree'])
|
||||
personnel = extract_value(last_task, 'personnel')
|
||||
training_val = extract_value(last_task, 'RequiredTraining')
|
||||
pi_supervisor = extract_value(last_task, 'PISupervisor')['value']
|
||||
review_complete = 'AllRequiredTraining' in training_val
|
||||
pi_uid = workflow.study.primary_investigator_id
|
||||
pi_details = ldapService.user_info(pi_uid)
|
||||
pi_details = LdapService.user_info(pi_uid)
|
||||
details = []
|
||||
details.append(pi_details)
|
||||
for person in personnel:
|
||||
uid = person['PersonnelComputingID']['value']
|
||||
details.append(ldapService.user_info(uid))
|
||||
details.append(LdapService.user_info(uid))
|
||||
|
||||
for person in details:
|
||||
output.append({
|
||||
record = {
|
||||
"study_id": approval.study_id,
|
||||
"pi_uid": pi_details.uid,
|
||||
"pi": pi_details.display_name,
|
||||
"name": person.display_name,
|
||||
"uid": person.uid,
|
||||
"email": person.email_address,
|
||||
"supervisor": "",
|
||||
"review_complete": review_complete,
|
||||
})
|
||||
}
|
||||
# We only know the PI's supervisor.
|
||||
if person.uid == pi_details.uid:
|
||||
record["supervisor"] = pi_supervisor
|
||||
|
||||
output.append(record)
|
||||
|
||||
except Exception as e:
|
||||
errors.append("Error pulling data for workflow #%i: %s" % (approval.workflow_id, str(e)))
|
||||
return {"results": output, "errors": errors }
|
||||
|
|
|
@ -122,7 +122,7 @@ def get_file_info(file_id):
|
|||
|
||||
def update_file_info(file_id, body):
|
||||
if file_id is None:
|
||||
raise ApiError('unknown_file', 'Please provide a valid File ID.')
|
||||
raise ApiError('no_such_file', 'Please provide a valid File ID.')
|
||||
|
||||
file_model = session.query(FileModel).filter_by(id=file_id).first()
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ def update_study(study_id, body):
|
|||
def get_study(study_id):
|
||||
study = StudyService.get_study(study_id)
|
||||
if (study is None):
|
||||
raise ApiError("Study not found", status_code=404)
|
||||
raise ApiError("unknown_study", 'The study "' + study_id + '" is not recognized.', status_code=404)
|
||||
return StudySchema().dump(study)
|
||||
|
||||
|
||||
|
|
|
@ -20,9 +20,9 @@ def render_markdown(data, template):
|
|||
data = json.loads(data)
|
||||
return template.render(**data)
|
||||
except UndefinedError as ue:
|
||||
raise ApiError(code="undefined field", message=ue.message)
|
||||
raise ApiError(code="undefined_field", message=ue.message)
|
||||
except Exception as e:
|
||||
raise ApiError(code="invalid", message=str(e))
|
||||
raise ApiError(code="invalid_render", message=str(e))
|
||||
|
||||
|
||||
def render_docx():
|
||||
|
@ -42,9 +42,9 @@ def render_docx():
|
|||
cache_timeout=-1 # Don't cache these files on the browser.
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ApiError(code="invalid", message=str(e))
|
||||
raise ApiError(code="undefined_field", message=str(e))
|
||||
except Exception as e:
|
||||
raise ApiError(code="invalid", message=str(e))
|
||||
raise ApiError(code="invalid_render", message=str(e))
|
||||
|
||||
|
||||
def list_scripts():
|
||||
|
|
|
@ -59,10 +59,7 @@ def sso_login():
|
|||
app.logger.info("SSO_LOGIN: Full URL: " + request.url)
|
||||
app.logger.info("SSO_LOGIN: User Id: " + uid)
|
||||
app.logger.info("SSO_LOGIN: Will try to redirect to : " + str(redirect))
|
||||
|
||||
ldap_service = LdapService()
|
||||
info = ldap_service.user_info(uid)
|
||||
|
||||
info = LdapService.user_info(uid)
|
||||
return _handle_login(info, redirect)
|
||||
|
||||
@app.route('/sso')
|
||||
|
@ -151,7 +148,7 @@ def backdoor(
|
|||
"""
|
||||
if not 'PRODUCTION' in app.config or not app.config['PRODUCTION']:
|
||||
|
||||
ldap_info = LdapService().user_info(uid)
|
||||
ldap_info = LdapService.user_info(uid)
|
||||
return _handle_login(ldap_info, redirect)
|
||||
else:
|
||||
raise ApiError('404', 'unknown')
|
||||
|
|
|
@ -71,15 +71,13 @@ class Approval(object):
|
|||
|
||||
if model.study:
|
||||
instance.title = model.study.title
|
||||
|
||||
ldap_service = LdapService()
|
||||
try:
|
||||
instance.approver = ldap_service.user_info(model.approver_uid)
|
||||
instance.primary_investigator = ldap_service.user_info(model.study.primary_investigator_id)
|
||||
instance.approver = LdapService.user_info(model.approver_uid)
|
||||
instance.primary_investigator = LdapService.user_info(model.study.primary_investigator_id)
|
||||
except ApiError as ae:
|
||||
app.logger.error("Ldap lookup failed for approval record %i" % model.id)
|
||||
|
||||
doc_dictionary = FileService.get_reference_data(FileService.DOCUMENT_LIST, 'code', ['id'])
|
||||
doc_dictionary = FileService.get_doc_dictionary()
|
||||
instance.associated_files = []
|
||||
for approval_file in model.approval_files:
|
||||
try:
|
||||
|
|
|
@ -82,7 +82,10 @@ class FileModel(db.Model):
|
|||
workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id'), nullable=True)
|
||||
workflow_id = db.Column(db.Integer, db.ForeignKey('workflow.id'), nullable=True)
|
||||
irb_doc_code = db.Column(db.String, nullable=True) # Code reference to the irb_documents.xlsx reference file.
|
||||
|
||||
# A request was made to delete the file, but we can't because there are
|
||||
# active approvals or running workflows that depend on it. So we archive
|
||||
# it instead, hide it in the interface.
|
||||
archived = db.Column(db.Boolean, default=False, nullable=False)
|
||||
|
||||
class File(object):
|
||||
@classmethod
|
||||
|
|
|
@ -24,7 +24,7 @@ class ApprovalService(object):
|
|||
"""Provides common tools for working with an Approval"""
|
||||
|
||||
@staticmethod
|
||||
def __one_approval_from_study(study, approver_uid = None, ignore_cancelled=False):
|
||||
def __one_approval_from_study(study, approver_uid = None, include_cancelled=True):
|
||||
"""Returns one approval, with all additional approvals as 'related_approvals',
|
||||
the main approval can be pinned to an approver with an optional argument.
|
||||
Will return null if no approvals exist on the study."""
|
||||
|
@ -32,7 +32,7 @@ class ApprovalService(object):
|
|||
related_approvals = []
|
||||
query = db.session.query(ApprovalModel).\
|
||||
filter(ApprovalModel.study_id == study.id)
|
||||
if ignore_cancelled:
|
||||
if not include_cancelled:
|
||||
query=query.filter(ApprovalModel.status != ApprovalStatus.CANCELED.value)
|
||||
|
||||
approvals = query.all()
|
||||
|
@ -49,35 +49,38 @@ class ApprovalService(object):
|
|||
return main_approval
|
||||
|
||||
@staticmethod
|
||||
def get_approvals_per_user(approver_uid):
|
||||
def get_approvals_per_user(approver_uid, include_cancelled=False):
|
||||
"""Returns a list of approval objects (not db models) for the given
|
||||
approver. """
|
||||
studies = db.session.query(StudyModel).join(ApprovalModel).\
|
||||
filter(ApprovalModel.approver_uid == approver_uid).all()
|
||||
approvals = []
|
||||
for study in studies:
|
||||
approval = ApprovalService.__one_approval_from_study(study, approver_uid)
|
||||
approval = ApprovalService.__one_approval_from_study(study, approver_uid, include_cancelled)
|
||||
if approval:
|
||||
approvals.append(approval)
|
||||
return approvals
|
||||
|
||||
@staticmethod
|
||||
def get_all_approvals(ignore_cancelled=False):
|
||||
def get_all_approvals(include_cancelled=True):
|
||||
"""Returns a list of all approval objects (not db models), one record
|
||||
per study, with any associated approvals grouped under the first approval."""
|
||||
studies = db.session.query(StudyModel).all()
|
||||
approvals = []
|
||||
for study in studies:
|
||||
approval = ApprovalService.__one_approval_from_study(study, ignore_cancelled=ignore_cancelled)
|
||||
approval = ApprovalService.__one_approval_from_study(study, include_cancelled=include_cancelled)
|
||||
if approval:
|
||||
approvals.append(approval)
|
||||
return approvals
|
||||
|
||||
@staticmethod
|
||||
def get_approvals_for_study(study_id):
|
||||
def get_approvals_for_study(study_id, include_cancelled=True):
|
||||
"""Returns an array of Approval objects for the study, it does not
|
||||
compute the related approvals."""
|
||||
db_approvals = session.query(ApprovalModel).filter_by(study_id=study_id).all()
|
||||
query = session.query(ApprovalModel).filter_by(study_id=study_id)
|
||||
if not include_cancelled:
|
||||
query = query.filter(ApprovalModel.status != ApprovalStatus.CANCELED.value)
|
||||
db_approvals = query.all()
|
||||
return [Approval.from_model(approval_model) for approval_model in db_approvals]
|
||||
|
||||
|
||||
|
|
|
@ -45,10 +45,8 @@ class FileService(object):
|
|||
|
||||
@staticmethod
|
||||
def is_allowed_document(code):
|
||||
data_model = FileService.get_reference_file_data(FileService.DOCUMENT_LIST)
|
||||
xls = ExcelFile(data_model.data)
|
||||
df = xls.parse(xls.sheet_names[0])
|
||||
return code in df['code'].values
|
||||
doc_dict = FileService.get_doc_dictionary()
|
||||
return code in doc_dict
|
||||
|
||||
@staticmethod
|
||||
def add_workflow_file(workflow_id, irb_doc_code, name, content_type, binary_data):
|
||||
|
@ -96,6 +94,7 @@ class FileService(object):
|
|||
def get_workflow_files(workflow_id):
|
||||
"""Returns all the file models associated with a running workflow."""
|
||||
return session.query(FileModel).filter(FileModel.workflow_id == workflow_id).\
|
||||
filter(FileModel.archived == False).\
|
||||
order_by(FileModel.id).all()
|
||||
|
||||
@staticmethod
|
||||
|
@ -127,7 +126,11 @@ class FileService(object):
|
|||
|
||||
md5_checksum = UUID(hashlib.md5(binary_data).hexdigest())
|
||||
if (latest_data_model is not None) and (md5_checksum == latest_data_model.md5_hash):
|
||||
# This file does not need to be updated, it's the same file.
|
||||
# This file does not need to be updated, it's the same file. If it is arhived,
|
||||
# then de-arvhive it.
|
||||
file_model.archived = False
|
||||
session.add(file_model)
|
||||
session.commit()
|
||||
return file_model
|
||||
|
||||
# Verify the extension
|
||||
|
@ -139,6 +142,7 @@ class FileService(object):
|
|||
else:
|
||||
file_model.type = FileType[file_extension]
|
||||
file_model.content_type = content_type
|
||||
file_model.archived = False # Unarchive the file if it is archived.
|
||||
|
||||
if latest_data_model is None:
|
||||
version = 1
|
||||
|
@ -188,7 +192,8 @@ class FileService(object):
|
|||
def get_files_for_study(study_id, irb_doc_code=None):
|
||||
query = session.query(FileModel).\
|
||||
join(WorkflowModel).\
|
||||
filter(WorkflowModel.study_id == study_id)
|
||||
filter(WorkflowModel.study_id == study_id).\
|
||||
filter(FileModel.archived == False)
|
||||
if irb_doc_code:
|
||||
query = query.filter(FileModel.irb_doc_code == irb_doc_code)
|
||||
return query.all()
|
||||
|
@ -208,6 +213,9 @@ class FileService(object):
|
|||
|
||||
if name:
|
||||
query = query.filter_by(name=name)
|
||||
|
||||
query = query.filter(FileModel.archived == False)
|
||||
|
||||
query = query.order_by(FileModel.id)
|
||||
|
||||
results = query.all()
|
||||
|
@ -270,11 +278,12 @@ class FileService(object):
|
|||
|
||||
@staticmethod
|
||||
def get_workflow_file_data(workflow, file_name):
|
||||
"""Given a SPIFF Workflow Model, tracks down a file with the given name in the database and returns its data"""
|
||||
"""This method should be deleted, find where it is used, and remove this method.
|
||||
Given a SPIFF Workflow Model, tracks down a file with the given name in the database and returns its data"""
|
||||
workflow_spec_model = FileService.find_spec_model_in_db(workflow)
|
||||
|
||||
if workflow_spec_model is None:
|
||||
raise ApiError(code="workflow_model_error",
|
||||
raise ApiError(code="unknown_workflow",
|
||||
message="Something is wrong. I can't find the workflow you are using.")
|
||||
|
||||
file_data_model = session.query(FileDataModel) \
|
||||
|
@ -316,6 +325,10 @@ class FileService(object):
|
|||
session.query(FileModel).filter_by(id=file_id).delete()
|
||||
session.commit()
|
||||
except IntegrityError as ie:
|
||||
app.logger.error("Failed to delete file: %i, due to %s" % (file_id, str(ie)))
|
||||
raise ApiError('file_integrity_error', "You are attempting to delete a file that is "
|
||||
"required by other records in the system.")
|
||||
# We can't delete the file or file data, because it is referenced elsewhere,
|
||||
# but we can at least mark it as deleted on the table.
|
||||
session.rollback()
|
||||
file_model = session.query(FileModel).filter_by(id=file_id).first()
|
||||
file_model.archived = True
|
||||
session.commit()
|
||||
app.logger.info("Failed to delete file, so archiving it instead. %i, due to %s" % (file_id, str(ie)))
|
||||
|
|
|
@ -4,7 +4,7 @@ from attr import asdict
|
|||
from ldap3.core.exceptions import LDAPExceptionError
|
||||
|
||||
from crc import app, db
|
||||
from ldap3 import Connection, Server, MOCK_SYNC
|
||||
from ldap3 import Connection, Server, MOCK_SYNC, RESTARTABLE
|
||||
|
||||
from crc.api.common import ApiError
|
||||
from crc.models.ldap import LdapModel, LdapSchema
|
||||
|
@ -19,37 +19,42 @@ class LdapService(object):
|
|||
cn_single_search = '(&(objectclass=person)(cn=%s*))'
|
||||
cn_double_search = '(&(objectclass=person)(&(cn=%s*)(cn=*%s*)))'
|
||||
|
||||
def __init__(self):
|
||||
if app.config['TESTING']:
|
||||
server = Server('my_fake_server')
|
||||
self.conn = Connection(server, client_strategy=MOCK_SYNC)
|
||||
file_path = os.path.abspath(os.path.join(app.root_path, '..', 'tests', 'data', 'ldap_response.json'))
|
||||
self.conn.strategy.entries_from_json(file_path)
|
||||
self.conn.bind()
|
||||
else:
|
||||
server = Server(app.config['LDAP_URL'], connect_timeout=app.config['LDAP_TIMEOUT_SEC'])
|
||||
self.conn = Connection(server,
|
||||
auto_bind=True,
|
||||
receive_timeout=app.config['LDAP_TIMEOUT_SEC'],
|
||||
)
|
||||
conn = None
|
||||
|
||||
def __del__(self):
|
||||
if self.conn:
|
||||
self.conn.unbind()
|
||||
@staticmethod
|
||||
def __get_conn():
|
||||
if not LdapService.conn:
|
||||
if app.config['TESTING']:
|
||||
server = Server('my_fake_server')
|
||||
conn = Connection(server, client_strategy=MOCK_SYNC)
|
||||
file_path = os.path.abspath(os.path.join(app.root_path, '..', 'tests', 'data', 'ldap_response.json'))
|
||||
conn.strategy.entries_from_json(file_path)
|
||||
conn.bind()
|
||||
else:
|
||||
server = Server(app.config['LDAP_URL'], connect_timeout=app.config['LDAP_TIMEOUT_SEC'])
|
||||
conn = Connection(server, auto_bind=True,
|
||||
receive_timeout=app.config['LDAP_TIMEOUT_SEC'],
|
||||
client_strategy=RESTARTABLE)
|
||||
LdapService.conn = conn
|
||||
return LdapService.conn
|
||||
|
||||
def user_info(self, uva_uid):
|
||||
|
||||
@staticmethod
|
||||
def user_info(uva_uid):
|
||||
user_info = db.session.query(LdapModel).filter(LdapModel.uid == uva_uid).first()
|
||||
if not user_info:
|
||||
search_string = LdapService.uid_search_string % uva_uid
|
||||
self.conn.search(LdapService.search_base, search_string, attributes=LdapService.attributes)
|
||||
if len(self.conn.entries) < 1:
|
||||
conn = LdapService.__get_conn()
|
||||
conn.search(LdapService.search_base, search_string, attributes=LdapService.attributes)
|
||||
if len(conn.entries) < 1:
|
||||
raise ApiError("missing_ldap_record", "Unable to locate a user with id %s in LDAP" % uva_uid)
|
||||
entry = self.conn.entries[0]
|
||||
entry = conn.entries[0]
|
||||
user_info = LdapModel.from_entry(entry)
|
||||
db.session.add(user_info)
|
||||
return user_info
|
||||
|
||||
def search_users(self, query, limit):
|
||||
@staticmethod
|
||||
def search_users(query, limit):
|
||||
if len(query.strip()) < 3:
|
||||
return []
|
||||
elif query.endswith(' '):
|
||||
|
@ -66,12 +71,13 @@ class LdapService(object):
|
|||
results = []
|
||||
app.logger.info(search_string)
|
||||
try:
|
||||
self.conn.search(LdapService.search_base, search_string, attributes=LdapService.attributes)
|
||||
conn = LdapService.__get_conn()
|
||||
conn.search(LdapService.search_base, search_string, attributes=LdapService.attributes)
|
||||
# Entries are returned as a generator, accessing entries
|
||||
# can make subsequent calls to the ldap service, so limit
|
||||
# those here.
|
||||
count = 0
|
||||
for entry in self.conn.entries:
|
||||
for entry in conn.entries:
|
||||
if count > limit:
|
||||
break
|
||||
results.append(LdapSchema().dump(LdapModel.from_entry(entry)))
|
||||
|
|
|
@ -103,7 +103,7 @@ class LookupService(object):
|
|||
workflow_id=workflow_model.id,
|
||||
name=file_name)
|
||||
if len(latest_files) < 1:
|
||||
raise ApiError("missing_file", "Unable to locate the lookup data file '%s'" % file_name)
|
||||
raise ApiError("invalid_enum", "Unable to locate the lookup data file '%s'" % file_name)
|
||||
else:
|
||||
data_model = latest_files[0]
|
||||
|
||||
|
@ -189,7 +189,7 @@ class LookupService(object):
|
|||
|
||||
@staticmethod
|
||||
def _run_ldap_query(query, limit):
|
||||
users = LdapService().search_users(query, limit)
|
||||
users = LdapService.search_users(query, limit)
|
||||
|
||||
"""Converts the user models into something akin to the
|
||||
LookupModel in models/file.py, so this can be returned in the same way
|
||||
|
|
|
@ -25,7 +25,7 @@ class ProtocolBuilderService(object):
|
|||
def get_studies(user_id) -> {}:
|
||||
ProtocolBuilderService.__enabled_or_raise()
|
||||
if not isinstance(user_id, str):
|
||||
raise ApiError("invalid_user_id", "This user id is invalid: " + str(user_id))
|
||||
raise ApiError("protocol_builder_error", "This user id is invalid: " + str(user_id))
|
||||
response = requests.get(ProtocolBuilderService.STUDY_URL % user_id)
|
||||
if response.ok and response.text:
|
||||
pb_studies = ProtocolBuilderStudySchema(many=True).loads(response.text)
|
||||
|
|
|
@ -206,8 +206,7 @@ class StudyService(object):
|
|||
@staticmethod
|
||||
def get_ldap_dict_if_available(user_id):
|
||||
try:
|
||||
ldap_service = LdapService()
|
||||
return LdapSchema().dump(ldap_service.user_info(user_id))
|
||||
return LdapSchema().dump(LdapService().user_info(user_id))
|
||||
except ApiError as ae:
|
||||
app.logger.info(str(ae))
|
||||
return {"error": str(ae)}
|
||||
|
@ -319,9 +318,9 @@ class StudyService(object):
|
|||
try:
|
||||
StudyService._create_workflow_model(study_model, workflow_spec)
|
||||
except WorkflowTaskExecException as wtee:
|
||||
errors.append(ApiError.from_task("workflow_execution_exception", str(wtee), wtee.task))
|
||||
errors.append(ApiError.from_task("workflow_startup_exception", str(wtee), wtee.task))
|
||||
except WorkflowException as we:
|
||||
errors.append(ApiError.from_task_spec("workflow_execution_exception", str(we), we.sender))
|
||||
errors.append(ApiError.from_task_spec("workflow_startup_exception", str(we), we.sender))
|
||||
return errors
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -82,7 +82,7 @@ class WorkflowService(object):
|
|||
processor = WorkflowProcessor(workflow_model, validate_only=True)
|
||||
except WorkflowException as we:
|
||||
WorkflowService.delete_test_data()
|
||||
raise ApiError.from_workflow_exception("workflow_execution_exception", str(we), we)
|
||||
raise ApiError.from_workflow_exception("workflow_validation_exception", str(we), we)
|
||||
|
||||
while not processor.bpmn_workflow.is_completed():
|
||||
try:
|
||||
|
@ -96,7 +96,7 @@ class WorkflowService(object):
|
|||
task.complete()
|
||||
except WorkflowException as we:
|
||||
WorkflowService.delete_test_data()
|
||||
raise ApiError.from_workflow_exception("workflow_execution_exception", str(we), we)
|
||||
raise ApiError.from_workflow_exception("workflow_validation_exception", str(we), we)
|
||||
|
||||
WorkflowService.delete_test_data()
|
||||
return processor.bpmn_workflow.last_task.data
|
||||
|
@ -162,7 +162,7 @@ class WorkflowService(object):
|
|||
options.append({"id": d.value, "label": d.label})
|
||||
return random.choice(options)
|
||||
else:
|
||||
raise ApiError.from_task("invalid_autocomplete", "The settings for this auto complete field "
|
||||
raise ApiError.from_task("unknown_lookup_option", "The settings for this auto complete field "
|
||||
"are incorrect: %s " % field.id, task)
|
||||
elif field.type == "long":
|
||||
return random.randint(1, 1000)
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 17597692d0b0
|
||||
Revises: 13424d5a6de8
|
||||
Create Date: 2020-06-03 17:33:56.454339
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '17597692d0b0'
|
||||
down_revision = '13424d5a6de8'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('file', sa.Column('archived', sa.Boolean(), nullable=True, default=False))
|
||||
op.execute("UPDATE file SET archived = false")
|
||||
op.alter_column('file', 'archived', nullable=False)
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('file', 'archived')
|
||||
# ### end Alembic commands ###
|
|
@ -1,8 +1,9 @@
|
|||
from tests.base_test import BaseTest
|
||||
|
||||
from crc import db
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
|
||||
|
||||
class TestFileService(BaseTest):
|
||||
"""Largely tested via the test_file_api, and time is tight, but adding new tests here."""
|
||||
|
||||
|
@ -46,12 +47,43 @@ class TestFileService(BaseTest):
|
|||
name="anything.png", content_type="text",
|
||||
binary_data=b'5678')
|
||||
|
||||
def test_replace_archive_file_unarchives_the_file_and_updates(self):
|
||||
self.load_example_data()
|
||||
self.create_reference_document()
|
||||
workflow = self.create_workflow('file_upload_form')
|
||||
processor = WorkflowProcessor(workflow)
|
||||
task = processor.next_task()
|
||||
irb_code = "UVACompl_PRCAppr" # The first file referenced in pb required docs.
|
||||
FileService.add_workflow_file(workflow_id=workflow.id,
|
||||
irb_doc_code=irb_code,
|
||||
name="anything.png", content_type="text",
|
||||
binary_data=b'1234')
|
||||
|
||||
# Archive the file
|
||||
file_models = FileService.get_workflow_files(workflow_id=workflow.id)
|
||||
self.assertEquals(1, len(file_models))
|
||||
file_model = file_models[0]
|
||||
file_model.archived = True
|
||||
db.session.add(file_model)
|
||||
|
||||
# Assure that the file no longer comes back.
|
||||
file_models = FileService.get_workflow_files(workflow_id=workflow.id)
|
||||
self.assertEquals(0, len(file_models))
|
||||
|
||||
# Add the file again with different data
|
||||
FileService.add_workflow_file(workflow_id=workflow.id,
|
||||
irb_doc_code=irb_code,
|
||||
name="anything.png", content_type="text",
|
||||
binary_data=b'5678')
|
||||
|
||||
file_models = FileService.get_workflow_files(workflow_id=workflow.id)
|
||||
self.assertEqual(1, len(file_models))
|
||||
|
||||
file_data = FileService.get_workflow_data_files(workflow_id=workflow.id)
|
||||
|
||||
self.assertEqual(1, len(file_data))
|
||||
self.assertEqual(2, file_data[0].version)
|
||||
self.assertEqual(b'5678', file_data[0].data)
|
||||
|
||||
def test_add_file_from_form_allows_multiple_files_with_different_names(self):
|
||||
self.load_example_data()
|
||||
|
|
|
@ -3,12 +3,14 @@ import json
|
|||
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
from crc import session
|
||||
from crc import session, db
|
||||
from crc.models.file import FileModel, FileType, FileSchema, FileModelSchema
|
||||
from crc.models.workflow import WorkflowSpecModel
|
||||
from crc.services.file_service import FileService
|
||||
from crc.services.workflow_processor import WorkflowProcessor
|
||||
from example_data import ExampleDataLoader
|
||||
from crc.services.approval_service import ApprovalService
|
||||
from crc.models.approval import ApprovalModel, ApprovalStatus
|
||||
|
||||
|
||||
class TestFilesApi(BaseTest):
|
||||
|
@ -46,6 +48,7 @@ class TestFilesApi(BaseTest):
|
|||
json_data = json.loads(rv.get_data(as_text=True))
|
||||
self.assertEqual(2, len(json_data))
|
||||
|
||||
|
||||
def test_create_file(self):
|
||||
self.load_example_data()
|
||||
spec = session.query(WorkflowSpecModel).first()
|
||||
|
@ -89,6 +92,39 @@ class TestFilesApi(BaseTest):
|
|||
self.assert_success(rv)
|
||||
|
||||
|
||||
def test_archive_file_no_longer_shows_up(self):
|
||||
self.load_example_data()
|
||||
self.create_reference_document()
|
||||
workflow = self.create_workflow('file_upload_form')
|
||||
processor = WorkflowProcessor(workflow)
|
||||
task = processor.next_task()
|
||||
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
||||
correct_name = task.task_spec.form.fields[0].id
|
||||
|
||||
data = {'file': (io.BytesIO(b"abcdef"), 'random_fact.svg')}
|
||||
rv = self.app.post('/v1.0/file?study_id=%i&workflow_id=%s&task_id=%i&form_field_key=%s' %
|
||||
(workflow.study_id, workflow.id, task.id, correct_name), data=data, follow_redirects=True,
|
||||
content_type='multipart/form-data', headers=self.logged_in_headers())
|
||||
|
||||
self.assert_success(rv)
|
||||
rv = self.app.get('/v1.0/file?workflow_id=%s' % workflow.id, headers=self.logged_in_headers())
|
||||
self.assert_success(rv)
|
||||
self.assertEquals(1, len(json.loads(rv.get_data(as_text=True))))
|
||||
|
||||
file_model = db.session.query(FileModel).filter(FileModel.workflow_id == workflow.id).all()
|
||||
self.assertEquals(1, len(file_model))
|
||||
file_model[0].archived = True
|
||||
db.session.commit()
|
||||
|
||||
rv = self.app.get('/v1.0/file?workflow_id=%s' % workflow.id, headers=self.logged_in_headers())
|
||||
self.assert_success(rv)
|
||||
self.assertEquals(0, len(json.loads(rv.get_data(as_text=True))))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def test_set_reference_file(self):
|
||||
file_name = "irb_document_types.xls"
|
||||
data = {'file': (io.BytesIO(b"abcdef"), "does_not_matter.xls")}
|
||||
|
@ -218,6 +254,41 @@ class TestFilesApi(BaseTest):
|
|||
rv = self.app.get('/v1.0/file/%i' % file.id, headers=self.logged_in_headers())
|
||||
self.assertEqual(404, rv.status_code)
|
||||
|
||||
def test_delete_file_after_approval(self):
|
||||
self.create_reference_document()
|
||||
workflow = self.create_workflow("empty_workflow")
|
||||
FileService.add_workflow_file(workflow_id=workflow.id,
|
||||
name="anything.png", content_type="text",
|
||||
binary_data=b'5678', irb_doc_code="UVACompl_PRCAppr")
|
||||
FileService.add_workflow_file(workflow_id=workflow.id,
|
||||
name="anotother_anything.png", content_type="text",
|
||||
binary_data=b'1234', irb_doc_code="Study_App_Doc")
|
||||
|
||||
ApprovalService.add_approval(study_id=workflow.study_id, workflow_id=workflow.id, approver_uid="dhf8r")
|
||||
|
||||
file = session.query(FileModel).\
|
||||
filter(FileModel.workflow_id == workflow.id).\
|
||||
filter(FileModel.name == "anything.png").first()
|
||||
self.assertFalse(file.archived)
|
||||
rv = self.app.get('/v1.0/file/%i' % file.id, headers=self.logged_in_headers())
|
||||
self.assert_success(rv)
|
||||
|
||||
rv = self.app.delete('/v1.0/file/%i' % file.id, headers=self.logged_in_headers())
|
||||
self.assert_success(rv)
|
||||
|
||||
session.refresh(file)
|
||||
self.assertTrue(file.archived)
|
||||
|
||||
ApprovalService.add_approval(study_id=workflow.study_id, workflow_id=workflow.id, approver_uid="dhf8r")
|
||||
|
||||
approvals = session.query(ApprovalModel)\
|
||||
.filter(ApprovalModel.status == ApprovalStatus.PENDING.value)\
|
||||
.filter(ApprovalModel.study_id == workflow.study_id).all()
|
||||
|
||||
self.assertEquals(1, len(approvals))
|
||||
self.assertEquals(1, len(approvals[0].approval_files))
|
||||
|
||||
|
||||
def test_change_primary_bpmn(self):
|
||||
self.load_example_data()
|
||||
spec = session.query(WorkflowSpecModel).first()
|
||||
|
|
|
@ -7,13 +7,13 @@ from crc.services.ldap_service import LdapService
|
|||
class TestLdapService(BaseTest):
|
||||
|
||||
def setUp(self):
|
||||
self.ldap_service = LdapService()
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_get_single_user(self):
|
||||
user_info = self.ldap_service.user_info("lb3dp")
|
||||
user_info = LdapService.user_info("lb3dp")
|
||||
self.assertIsNotNone(user_info)
|
||||
self.assertEqual("lb3dp", user_info.uid)
|
||||
self.assertEqual("Laura Barnes", user_info.display_name)
|
||||
|
@ -27,7 +27,7 @@ class TestLdapService(BaseTest):
|
|||
|
||||
def test_find_missing_user(self):
|
||||
try:
|
||||
user_info = self.ldap_service.user_info("nosuch")
|
||||
user_info = LdapService.user_info("nosuch")
|
||||
self.assertFalse(True, "An API error should be raised.")
|
||||
except ApiError as ae:
|
||||
self.assertEqual("missing_ldap_record", ae.code)
|
|
@ -208,7 +208,6 @@ class TestTasksApi(BaseTest):
|
|||
self.assert_success(rv)
|
||||
|
||||
|
||||
|
||||
def test_get_documentation_populated_in_end(self):
|
||||
self.load_example_data()
|
||||
workflow = self.create_workflow('random_fact')
|
||||
|
|
|
@ -71,7 +71,7 @@ class TestWorkflowSpecValidation(BaseTest):
|
|||
self.load_example_data()
|
||||
errors = self.validate_workflow("invalid_expression")
|
||||
self.assertEqual(2, len(errors))
|
||||
self.assertEqual("workflow_execution_exception", errors[0]['code'])
|
||||
self.assertEqual("workflow_validation_exception", errors[0]['code'])
|
||||
self.assertEqual("ExclusiveGateway_003amsm", errors[0]['task_id'])
|
||||
self.assertEqual("Has Bananas Gateway", errors[0]['task_name'])
|
||||
self.assertEqual("invalid_expression.bpmn", errors[0]['file_name'])
|
||||
|
@ -92,7 +92,7 @@ class TestWorkflowSpecValidation(BaseTest):
|
|||
self.load_example_data()
|
||||
errors = self.validate_workflow("invalid_script")
|
||||
self.assertEqual(2, len(errors))
|
||||
self.assertEqual("workflow_execution_exception", errors[0]['code'])
|
||||
self.assertEqual("workflow_validation_exception", errors[0]['code'])
|
||||
self.assertTrue("NoSuchScript" in errors[0]['message'])
|
||||
self.assertEqual("Invalid_Script_Task", errors[0]['task_id'])
|
||||
self.assertEqual("An Invalid Script Reference", errors[0]['task_name'])
|
||||
|
|
Loading…
Reference in New Issue