import json from datetime import datetime from SpiffWorkflow.util.metrics import timeit, firsttime, sincetime from flask import g, send_file from sqlalchemy.exc import IntegrityError from crc import session from crc.api.common import ApiError, ApiErrorSchema from crc.models.study import Study, StudyEventType, StudyModel, StudySchema, StudyForUpdateSchema, \ StudyStatus, StudyAssociatedSchema, Category from crc.models.task_log import TaskLogQuery, TaskLogQuerySchema from crc.services.spreadsheet_service import SpreadsheetService from crc.services.study_service import StudyService from crc.services.task_logging_service import TaskLoggingService from crc.services.user_service import UserService from crc.services.workflow_processor import WorkflowProcessor from crc.services.workflow_service import WorkflowService from crc.services.workflow_spec_service import WorkflowSpecService from crc.api.user import verify_token import io def add_study(body): # fixme: Remove this method. We don't add a study this way except in testing. """ This method seems to be used by one test, and no where else! Or any study like object. Body should include a title, and primary_investigator_id """ """Or any study like object. Body should include a title """ if 'title' not in body: raise ApiError("missing_title", "Can't create a new study without a title.") study_model = StudyModel(user_uid=UserService.current_user().uid, title=body['title'], last_updated=datetime.utcnow(), status=StudyStatus.in_progress, review_type=body['review_type']) session.add(study_model) StudyService.add_study_update_event(study_model, status=StudyStatus.in_progress, event_type=StudyEventType.user, user_uid=g.user.uid) spec_service = WorkflowSpecService() specs = spec_service.get_specs() categories = spec_service.get_categories() errors = StudyService.add_all_workflow_specs_to_study(study_model, specs) session.commit() master_workflow_results = __run_master_spec(study_model, spec_service.master_spec) study = StudyService().get_study(study_model.id, categories, master_workflow_results=master_workflow_results, process_categories=True) study_data = StudySchema().dump(study) study_data["errors"] = ApiErrorSchema(many=True).dump(errors) return study_data def __run_master_spec(study_model, master_spec): """Runs the master workflow spec to get details on the status of each workflow. This is a fairly expensive call.""" """Uses the Top Level Workflow to calculate the status of the study, and its workflow models.""" if not master_spec: raise ApiError("missing_master_spec", "No specifications are currently marked as the master spec.") return WorkflowProcessor.run_master_spec(master_spec, study_model) def update_study(study_id, body): spec_service = WorkflowSpecService() categories = spec_service.get_categories() """Pretty limited, but allows manual modifications to the study status """ if study_id is None: raise ApiError('unknown_study', 'Please provide a valid Study ID.') study_model = session.query(StudyModel).filter_by(id=study_id).first() if study_model is None: raise ApiError('unknown_study', 'The study "' + study_id + '" is not recognized.') study: Study = StudyForUpdateSchema().load(body) status = StudyStatus(study.status) study_model.last_updated = datetime.utcnow() if study_model.status != status: study_model.status = status StudyService.add_study_update_event(study_model, status, StudyEventType.user, user_uid=UserService.current_user().uid if UserService.has_user() else None, comment='' if not hasattr(study, 'comment') else study.comment, ) if status == StudyStatus.open_for_enrollment: study_model.enrollment_date = study.enrollment_date session.add(study_model) session.commit() if status == StudyStatus.abandoned or status == StudyStatus.hold: WorkflowService.process_workflows_for_cancels(study_id) # Need to reload the full study to return it to the frontend study = StudyService.get_study(study_id, categories) return StudySchema().dump(study) def get_study(study_id, update_status=False): spec_service = WorkflowSpecService() categories = spec_service.get_categories() master_workflow_results = [] if update_status: study_model = session.query(StudyModel).filter(StudyModel.id == study_id).first() master_workflow_results = __run_master_spec(study_model, spec_service.master_spec) study = StudyService().get_study(study_id, categories, master_workflow_results=master_workflow_results, process_categories=True) if (study is None): raise ApiError("unknown_study", 'The study "' + study_id + '" is not recognized.', status_code=404) return StudySchema().dump(study) def get_study_associates(study_id): return StudyAssociatedSchema(many=True).dump(StudyService.get_study_associates(study_id)) def get_logs_for_study(study_id, body): task_log_query = TaskLogQuery(**body) task_log_query.study_id = study_id # Force the study id return TaskLogQuerySchema().dump( TaskLoggingService.get_logs_for_study_paginated(study_id, task_log_query)) def download_logs_for_study(study_id, auth_token): # Download links incorporate an auth token in the request for direct download if not verify_token(auth_token): raise ApiError('not_authenticated', 'You need to include an authorization token in the URL with this') title = f'Study {study_id}' logs, headers = TaskLoggingService.get_log_data_for_download(study_id) spreadsheet = SpreadsheetService.create_spreadsheet(logs, headers, title) return send_file( io.BytesIO(spreadsheet), attachment_filename='logs.xlsx', mimetype='xlsx', cache_timeout=-1, # Don't cache these files on the browser. last_modified=datetime.now(), as_attachment=True ) def delete_study(study_id): try: StudyService.delete_study(study_id) except IntegrityError as ie: session.rollback() message = "Failed to delete Study #%i due to an Integrity Error: %s" % (study_id, str(ie)) raise ApiError(code="study_integrity_error", message=message) def user_studies(): """Returns all the studies associated with the current user. """ user = UserService.current_user(allow_admin_impersonate=True) spec_service = WorkflowSpecService() specs = spec_service.get_specs() cats = spec_service.get_categories() StudyService.synch_with_protocol_builder_if_enabled(user, specs) studies = StudyService().get_studies_for_user(user, categories=cats) if len(studies) == 0: studies = StudyService().get_studies_for_user(user, categories=cats, include_invalid=True) if len(studies) > 0: 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) return results def all_studies(): """Returns all studies (regardless of user) with submitted files""" studies = StudyService.get_all_studies_with_files() results = StudySchema(many=True).dump(studies) return results