From 57a7c7fa5401d940eabf9eb3aedcc5ee45dd161e Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Fri, 5 Jun 2020 13:39:52 -0600 Subject: [PATCH 1/3] Approve/deny fixes --- crc/api/approval.py | 4 ++++ crc/services/approval_service.py | 13 +++++++------ crc/services/mails.py | 4 ---- crc/static/templates/mails/ramp_up_denied.html | 2 +- tests/test_approvals_api.py | 4 ++-- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/crc/api/approval.py b/crc/api/approval.py index 9c42c82e..5c403eec 100644 --- a/crc/api/approval.py +++ b/crc/api/approval.py @@ -111,5 +111,9 @@ def update_approval(approval_id, body): session.add(approval_model) session.commit() + # Called only to send emails + approver = body['approver']['uid'] + ApprovalService.update_approval(approval_id, approver) + result = ApprovalSchema().dump(approval_model) return result diff --git a/crc/services/approval_service.py b/crc/services/approval_service.py index 04b0db65..86547cb3 100644 --- a/crc/services/approval_service.py +++ b/crc/services/approval_service.py @@ -85,13 +85,14 @@ class ApprovalService(object): @staticmethod - def update_approval(approval_id, approver_uid, status): + def update_approval(approval_id, approver_uid): """Update a specific approval""" db_approval = session.query(ApprovalModel).get(approval_id) + status = db_approval.status if db_approval: - db_approval.status = status - session.add(db_approval) - session.commit() + # db_approval.status = status + # session.add(db_approval) + # session.commit() if status == ApprovalStatus.APPROVED.value: # second_approval = ApprovalModel().query.filter_by( # study_id=db_approval.study_id, workflow_id=db_approval.workflow_id, @@ -99,7 +100,7 @@ class ApprovalService(object): # if second_approval: # send rrp approval request for second approver ldap_service = LdapService() - pi_user_info = ldap_service.user_info(model.study.primary_investigator_id) + pi_user_info = ldap_service.user_info(db_approval.study.primary_investigator_id) approver_info = ldap_service.user_info(approver_uid) # send rrp submission send_ramp_up_approved_email( @@ -109,7 +110,7 @@ class ApprovalService(object): ) elif status == ApprovalStatus.DECLINED.value: ldap_service = LdapService() - pi_user_info = ldap_service.user_info(model.study.primary_investigator_id) + pi_user_info = ldap_service.user_info(db_approval.study.primary_investigator_id) approver_info = ldap_service.user_info(approver_uid) # send rrp submission send_ramp_up_denied_email( diff --git a/crc/services/mails.py b/crc/services/mails.py index d6de3ff6..2a80457c 100644 --- a/crc/services/mails.py +++ b/crc/services/mails.py @@ -11,7 +11,6 @@ def send_ramp_up_submission_email(sender, recipients, approver_1, approver_2=Non msg = Message('Research Ramp-up Plan Submitted', sender=sender, recipients=recipients) - from crc import env, mail template = env.get_template('ramp_up_submission.txt') template_vars = {'approver_1': approver_1, 'approver_2': approver_2} @@ -28,7 +27,6 @@ def send_ramp_up_approval_request_email(sender, recipients, primary_investigator msg = Message('Research Ramp-up Plan Approval Request', sender=sender, recipients=recipients) - from crc import env, mail template = env.get_template('ramp_up_approval_request.txt') template_vars = {'primary_investigator': primary_investigator} @@ -45,7 +43,6 @@ def send_ramp_up_approval_request_first_review_email(sender, recipients, primary msg = Message('Research Ramp-up Plan Approval Request', sender=sender, recipients=recipients) - from crc import env, mail template = env.get_template('ramp_up_approval_request_first_review.txt') template_vars = {'primary_investigator': primary_investigator} @@ -62,7 +59,6 @@ def send_ramp_up_approved_email(sender, recipients, approver_1, approver_2=None) msg = Message('Research Ramp-up Plan Approved', sender=sender, recipients=recipients) - from crc import env, mail template = env.get_template('ramp_up_approved.txt') template_vars = {'approver_1': approver_1, 'approver_2': approver_2} diff --git a/crc/static/templates/mails/ramp_up_denied.html b/crc/static/templates/mails/ramp_up_denied.html index 9c978a80..7a40c1ea 100644 --- a/crc/static/templates/mails/ramp_up_denied.html +++ b/crc/static/templates/mails/ramp_up_denied.html @@ -1 +1 @@ -

Your Research Ramp-up Plan has been denied by {{ approver_1 }}. Please return to the Research Ramp-up Plan application and review the comments from {{ approver_1 }} on the home page. Next, open the application and locate the first step where changes are needed. Continue to complete additional steps saving your work along the way. Review your revised Research Ramp-up Plan and res-submit for approval.

\ No newline at end of file +

Your Research Ramp-up Plan has been denied by {{ approver }}. Please return to the Research Ramp-up Plan application and review the comments from {{ approver }} on the home page. Next, open the application and locate the first step where changes are needed. Continue to complete additional steps saving your work along the way. Review your revised Research Ramp-up Plan and res-submit for approval.

\ No newline at end of file diff --git a/tests/test_approvals_api.py b/tests/test_approvals_api.py index 6d95be39..da1c2076 100644 --- a/tests/test_approvals_api.py +++ b/tests/test_approvals_api.py @@ -106,7 +106,7 @@ class TestApprovals(BaseTest): def test_accept_approval(self): approval = session.query(ApprovalModel).filter_by(approver_uid='dhf8r').first() data = {'id': approval.id, - "approver_uid": "dhf8r", + "approver": {"uid": "dhf8r"}, 'message': "Approved. I like the cut of your jib.", 'status': ApprovalStatus.APPROVED.value} @@ -127,7 +127,7 @@ class TestApprovals(BaseTest): def test_decline_approval(self): approval = session.query(ApprovalModel).filter_by(approver_uid='dhf8r').first() data = {'id': approval.id, - "approver_uid": "dhf8r", + "approver": {"uid": "dhf8r"}, 'message': "Approved. I find the cut of your jib lacking.", 'status': ApprovalStatus.DECLINED.value} From 663da57d8beb768580c07624ea9d7d590f4c429d Mon Sep 17 00:00:00 2001 From: Carlos Lopez Date: Fri, 5 Jun 2020 13:54:37 -0600 Subject: [PATCH 2/3] Config can read smtp values from environment now --- config/default.py | 8 +++++++- crc/__init__.py | 6 ------ crc/services/mails.py | 1 + 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/config/default.py b/config/default.py index bac744fd..ae17bcfa 100644 --- a/config/default.py +++ b/config/default.py @@ -44,5 +44,11 @@ PB_STUDY_DETAILS_URL = environ.get('PB_STUDY_DETAILS_URL', default=PB_BASE_URL + LDAP_URL = environ.get('LDAP_URL', default="ldap.virginia.edu").strip('/') # No trailing slash or http:// LDAP_TIMEOUT_SEC = int(environ.get('LDAP_TIMEOUT_SEC', default=3)) -# Fallback emails +# Email configuration FALLBACK_EMAILS = ['askresearch@virginia.edu', 'sartographysupport@googlegroups.com'] +MAIL_SERVER = environ.get('MAIL_SERVER', default='smtp.mailtrap.io') +MAIL_PORT = environ.get('MAIL_PORT', default=2525) +MAIL_USE_SSL = environ.get('MAIL_USE_SSL', default=False) +MAIL_USE_TLS = environ.get('MAIL_USE_TLS', default=True) +MAIL_USERNAME = environ.get('MAIL_USERNAME', default='5f012d0108d374') +MAIL_PASSWORD = environ.get('MAIL_PASSWORD', default='08442c04e98d50') diff --git a/crc/__init__.py b/crc/__init__.py index 66b91b63..e77864b9 100644 --- a/crc/__init__.py +++ b/crc/__init__.py @@ -54,12 +54,6 @@ if app.config['ENABLE_SENTRY']: template_dir = os.getcwd() + '/crc/static/templates/mails' env = Environment(loader=FileSystemLoader(template_dir)) # Mail settings -app.config['MAIL_SERVER']='smtp.mailtrap.io' -app.config['MAIL_PORT'] = 2525 -app.config['MAIL_USERNAME'] = '5f012d0108d374' -app.config['MAIL_PASSWORD'] = '08442c04e98d50' -app.config['MAIL_USE_TLS'] = True -app.config['MAIL_USE_SSL'] = False mail = Mail(app) print('=== USING THESE CONFIG SETTINGS: ===') diff --git a/crc/services/mails.py b/crc/services/mails.py index 2a80457c..994914d4 100644 --- a/crc/services/mails.py +++ b/crc/services/mails.py @@ -59,6 +59,7 @@ def send_ramp_up_approved_email(sender, recipients, approver_1, approver_2=None) msg = Message('Research Ramp-up Plan Approved', sender=sender, recipients=recipients) + from crc import env, mail template = env.get_template('ramp_up_approved.txt') template_vars = {'approver_1': approver_1, 'approver_2': approver_2} From 1d3f98b381730fdd3db4bca80dd1aba027dbd1d0 Mon Sep 17 00:00:00 2001 From: Aaron Louie Date: Fri, 5 Jun 2020 22:19:37 -0400 Subject: [PATCH 3/3] Adds endpoint to quickly get counts of approvals in each status group for a user --- crc/api.yml | 46 ++++++++++++++++++++++++++++++++++++- crc/api/approval.py | 55 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 99 insertions(+), 2 deletions(-) diff --git a/crc/api.yml b/crc/api.yml index 638eb787..3a0875cc 100644 --- a/crc/api.yml +++ b/crc/api.yml @@ -806,12 +806,34 @@ paths: type: array items: $ref: "#/components/schemas/Script" + /approval-counts: + parameters: + - name: as_user + in: query + required: false + description: If provided, returns the approval counts for that user. + schema: + type: string + get: + operationId: crc.api.approval.get_approval_counts + summary: Provides counts for approvals by status for the given user, or all users if no user is provided + tags: + - Approvals + responses: + '200': + description: An dictionary of Approval Statuses and the counts for each + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/ApprovalCounts" /approval: parameters: - name: status in: query required: false - description: If set to true, returns all the approvals with any status. + description: If provided, returns just approvals for the given status. schema: type: string - name: as_user @@ -1286,4 +1308,26 @@ components: type: number format: integer example: 5 + ApprovalCounts: + properties: + PENDING: + type: number + format: integer + example: 5 + APPROVED: + type: number + format: integer + example: 5 + DECLINED: + type: number + format: integer + example: 5 + CANCELED: + type: number + format: integer + example: 5 + AWAITING: + type: number + format: integer + example: 5 diff --git a/crc/api/approval.py b/crc/api/approval.py index b23315df..bc20c624 100644 --- a/crc/api/approval.py +++ b/crc/api/approval.py @@ -13,6 +13,56 @@ from crc.services.approval_service import ApprovalService from crc.services.ldap_service import LdapService +# Returns counts of approvals in each status group assigned to the given user. +# The goal is to return results as quickly as possible. +def get_approval_counts(as_user=None): + uid = as_user or g.user.uid + + db_user_approvals = db.session.query(ApprovalModel)\ + .filter_by(approver_uid=uid)\ + .filter(ApprovalModel.status != ApprovalStatus.CANCELED.name)\ + .all() + + study_ids = [a.study_id for a in db_user_approvals] + print('study_ids', study_ids) + + db_other_approvals = db.session.query(ApprovalModel)\ + .filter(ApprovalModel.study_id.in_(study_ids))\ + .filter(ApprovalModel.approver_uid != uid)\ + .filter(ApprovalModel.status != ApprovalStatus.CANCELED.name)\ + .all() + + # Make a dict of the other approvals where the key is the study id and the value is the approval + # TODO: This won't work if there are more than 2 approvals with the same study_id + other_approvals = {} + for approval in db_other_approvals: + other_approvals[approval.study_id] = approval + + counts = {} + for status in ApprovalStatus: + counts[status.name] = 0 + + for approval in db_user_approvals: + # Check if another approval has the same study id + if approval.study_id in other_approvals: + other_approval = other_approvals[approval.study_id] + + # Other approval takes precedence over this one + if other_approval.id < approval.id: + if other_approval.status == ApprovalStatus.PENDING.name: + counts[ApprovalStatus.AWAITING.name] += 1 + elif other_approval.status == ApprovalStatus.DECLINED.name: + counts[ApprovalStatus.DECLINED.name] += 1 + elif other_approval.status == ApprovalStatus.CANCELED.name: + counts[ApprovalStatus.CANCELED.name] += 1 + elif other_approval.status == ApprovalStatus.APPROVED.name: + counts[approval.status] += 1 + else: + counts[approval.status] += 1 + + return counts + + def get_approvals(status=None, as_user=None): #status = ApprovalStatus.PENDING.value user = g.user.uid @@ -31,7 +81,7 @@ def get_approvals_for_study(study_id=None): return results -# ----- Being decent into madness ---- # +# ----- Begin descent into madness ---- # def get_csv(): """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""" @@ -81,12 +131,14 @@ def get_csv(): errors.append("Error pulling data for workflow #%i: %s" % (approval.workflow_id, str(e))) return {"results": output, "errors": errors } + def extract_value(task, key): if key in task['data']: return pickle.loads(b64decode(task['data'][key]['__bytes__'])) else: return "" + def find_task(uuid, task): if task['id']['__uuid__'] == uuid: return task @@ -96,6 +148,7 @@ def find_task(uuid, task): return task # ----- come back to the world of the living ---- # + def update_approval(approval_id, body): if approval_id is None: raise ApiError('unknown_approval', 'Please provide a valid Approval ID.')