From e72cf27fe3c06c68cc0597c6eb95c0bf8dec31fd Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 11:41:44 -0400 Subject: [PATCH 01/12] Return email model from email script. --- crc/scripts/email.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crc/scripts/email.py b/crc/scripts/email.py index bca72827..8ab14f45 100644 --- a/crc/scripts/email.py +++ b/crc/scripts/email.py @@ -3,6 +3,7 @@ import traceback from crc import app, session from crc.api.common import ApiError +from crc.models.email import EmailModelSchema from crc.models.file import FileModel, CONTENT_TYPES from crc.models.workflow import WorkflowModel from crc.services.document_service import DocumentService @@ -92,7 +93,7 @@ email(subject="My Subject", recipients="user@example.com", attachments=['Study_A print(repr(traceback.format_exception(exc_type, exc_value, exc_traceback))) raise e - return email_model.id + return EmailModelSchema().dump(email_model) def get_email_addresses(self, users, study_id): emails = [] From dcb06f3753e983a27480c25c1ce461534c208a48 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 11:42:01 -0400 Subject: [PATCH 02/12] Schema for returning email model --- crc/models/email.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crc/models/email.py b/crc/models/email.py index a549cfdb..cf2bb543 100644 --- a/crc/models/email.py +++ b/crc/models/email.py @@ -23,9 +23,8 @@ class EmailModel(db.Model): class EmailModelSchema(ma.Schema): - # TODO: clean this us. Do we need load_instance and unknown? + class Meta: model = EmailModel - load_instance = True - additional = ["id", "subject", "sender", "recipients", "timestamp"] - unknown = INCLUDE + fields = ["id", "subject", "sender", "recipients", "cc", "bcc", "content", "content_html", + "study_id", "timestamp", "workflow_spec_id"] From f1dbda7f95590a672f5b8fc833a1744f7caef69a Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 11:46:57 -0400 Subject: [PATCH 03/12] Fix timestamp field to use (UTC) timezone --- .../ba6df7e560a1_modify_email_timestamp.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 migrations/versions/ba6df7e560a1_modify_email_timestamp.py diff --git a/migrations/versions/ba6df7e560a1_modify_email_timestamp.py b/migrations/versions/ba6df7e560a1_modify_email_timestamp.py new file mode 100644 index 00000000..58f870de --- /dev/null +++ b/migrations/versions/ba6df7e560a1_modify_email_timestamp.py @@ -0,0 +1,24 @@ +"""modify email timestamp + +Revision ID: ba6df7e560a1 +Revises: 6d8ceb1c18cb +Create Date: 2021-10-13 10:54:23.894034 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ba6df7e560a1' +down_revision = '6d8ceb1c18cb' +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("alter table email alter column timestamp type timestamp with time zone") + + +def downgrade(): + op.execute("alter table email alter column timestamp type timestamp without time zone") From a1a06f06caebc40dea4f208bb3030d25b263160e Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 11:47:40 -0400 Subject: [PATCH 04/12] We now get an email model back --- tests/data/email/email.bpmn | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/data/email/email.bpmn b/tests/data/email/email.bpmn index 40489409..389ba06d 100644 --- a/tests/data/email/email.bpmn +++ b/tests/data/email/email.bpmn @@ -19,15 +19,12 @@ Email content to be delivered to {{ ApprvlApprvr1 }} --- **Test Some Formatting** - _UVA Tracking Number:_ {{ 321 }} - - + _UVA Tracking Number:_ {{ 321 }} Flow_08n2npe Flow_1xlrgne subject="Camunda Email Subject" recipients=[ApprvlApprvr1,PIComputingID] -email_id = email(subject=subject,recipients=recipients) - +email_model = email(subject=subject,recipients=recipients) From bd6a2f30058fd159c32a9ea7d3217c373300c0e8 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 11:48:27 -0400 Subject: [PATCH 05/12] Check the model we get back from the email script Make sure timestamp is UTC --- tests/emails/test_email_script.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/emails/test_email_script.py b/tests/emails/test_email_script.py index 0b1ec499..b84e8bbd 100644 --- a/tests/emails/test_email_script.py +++ b/tests/emails/test_email_script.py @@ -1,6 +1,7 @@ from tests.base_test import BaseTest from crc import mail from crc.models.email import EmailModel +import datetime class TestEmailScript(BaseTest): @@ -35,5 +36,16 @@ class TestEmailScript(BaseTest): # Correct From field self.assertEqual('uvacrconnect@virginia.edu', outbox[0].sender) - db_emails = EmailModel.query.count() - self.assertEqual(db_emails, 1) + # Make sure we log the email + # Grab model for comparison below + db_emails = EmailModel.query.all() + self.assertEqual(len(db_emails), 1) + + # Check whether we get a good email model back from the script + self.assertIn('email_model', workflow_api.next_task.data) + self.assertEqual(db_emails[0].recipients, workflow_api.next_task.data['email_model']['recipients']) + self.assertEqual(db_emails[0].sender, workflow_api.next_task.data['email_model']['sender']) + self.assertEqual(db_emails[0].subject, workflow_api.next_task.data['email_model']['subject']) + + # Make sure timestamp is UTC + self.assertEqual(db_emails[0].timestamp.tzinfo, datetime.timezone.utc) From 3ae00d190dfe3dc9abf338e498694b7876f8eb53 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 12:03:48 -0400 Subject: [PATCH 06/12] Raise error if we have a problem sending email. This should be processed by Spiff ultimately. --- crc/services/email_service.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crc/services/email_service.py b/crc/services/email_service.py index 86810faa..20006827 100644 --- a/crc/services/email_service.py +++ b/crc/services/email_service.py @@ -47,9 +47,11 @@ class EmailService(object): msg.attach(file['name'], file['type'], file_data.data) mail.send(msg) + except Exception as e: app.logger.error('An exception happened in EmailService', exc_info=True) app.logger.error(str(e)) + raise e db.session.add(email_model) db.session.commit() From 771019d9cad2310c063ab3f7d4905fad660f8d1d Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 12:05:08 -0400 Subject: [PATCH 07/12] Clarify call to email service. We now use the words content and content_html --- crc/api/tools.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crc/api/tools.py b/crc/api/tools.py index e889f73b..64ad8a0b 100644 --- a/crc/api/tools.py +++ b/crc/api/tools.py @@ -96,11 +96,12 @@ def evaluate_python_expression(body): except Exception as e: return {"result": False, "expression": body['expression'], "key": body['key'], "error": str(e)} + def send_test_email(subject, address, message, data=None): - rendered, wrapped = EmailService().get_rendered_content(message, data) + content, content_html = EmailService().get_rendered_content(message, data) EmailService.add_email( subject=subject, sender=DEFAULT_SENDER, recipients=[address], - content=rendered, - content_html=wrapped) + content=content, + content_html=content_html) From b3d515bf680b7a5a4c271632acfcc90897b9a97d Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 13:23:18 -0400 Subject: [PATCH 08/12] Test for condition where email_service.add_email raises an exception. --- tests/emails/test_email_service.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/emails/test_email_service.py b/tests/emails/test_email_service.py index 1cb36525..b045118f 100644 --- a/tests/emails/test_email_service.py +++ b/tests/emails/test_email_service.py @@ -3,6 +3,7 @@ from tests.base_test import BaseTest from crc import session from crc.models.email import EmailModel from crc.services.email_service import EmailService +from unittest.mock import patch class TestEmailService(BaseTest): @@ -10,7 +11,6 @@ class TestEmailService(BaseTest): def test_add_email(self): self.load_example_data() study = self.create_study() - workflow = self.create_workflow('random_fact') subject = 'Email Subject' sender = 'sender@sartography.com' @@ -42,3 +42,25 @@ class TestEmailService(BaseTest): self.assertEqual(email_model.content, content) self.assertEqual(email_model.content_html, content_html) self.assertEqual(email_model.study, None) + + @patch('crc.services.email_service.EmailService.add_email') + def test_add_email_with_error(self, mock_response): + + self.load_example_data() + mock_response.return_value.ok = True + mock_response.side_effect = Exception("This is my exception!") + + study = self.create_study() + + subject = 'Email Subject' + sender = 'sender@sartography.com' + recipients = ['recipient@sartography.com', 'back@sartography.com'] + content = 'Content for this email' + content_html = '

Hypertext Markup Language content for this email

' + + # Make sure we generate an error + with self.assertRaises(Exception) as e: + EmailService.add_email(subject=subject, sender=sender, recipients=recipients, + content=content, content_html=content_html, study_id=study.id) + # Make sure it's the error we want + self.assertEqual('This is my exception!', e.exception.args[0]) From 2f3fe59a0f96beabc1195ce42314a74a43194fe5 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 13:40:14 -0400 Subject: [PATCH 09/12] Test for Exception when email service fails --- tests/emails/test_email_script.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/emails/test_email_script.py b/tests/emails/test_email_script.py index b84e8bbd..8176a9e0 100644 --- a/tests/emails/test_email_script.py +++ b/tests/emails/test_email_script.py @@ -2,6 +2,7 @@ from tests.base_test import BaseTest from crc import mail from crc.models.email import EmailModel import datetime +from unittest.mock import patch class TestEmailScript(BaseTest): @@ -49,3 +50,21 @@ class TestEmailScript(BaseTest): # Make sure timestamp is UTC self.assertEqual(db_emails[0].timestamp.tzinfo, datetime.timezone.utc) + + @patch('crc.services.email_service.EmailService.add_email') + def test_email_raises_exception(self, mock_response): + self.load_example_data() + mock_response.return_value.ok = True + mock_response.side_effect = Exception("This is my exception!") + + workflow = self.create_workflow('email') + + task_data = { + 'PIComputingID': 'dhf8r@virginia.edu', + 'ApprvlApprvr1': 'lb3dp@virginia.edu' + } + task = self.get_workflow_api(workflow).next_task + + with mail.record_messages() as outbox: + with self.assertRaises(Exception) as e: + self.complete_form(workflow, task, task_data) From 8015d35424ac9bb1800cc0d06dcf341a4065306f Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Wed, 13 Oct 2021 15:36:37 -0400 Subject: [PATCH 10/12] Script to get localtime from a UTC datetime --- crc/scripts/get_localtime.py | 19 ++++++ tests/data/get_localtime/get_localtime.bpmn | 72 +++++++++++++++++++++ tests/scripts/test_get_localtime.py | 21 ++++++ 3 files changed, 112 insertions(+) create mode 100644 crc/scripts/get_localtime.py create mode 100644 tests/data/get_localtime/get_localtime.bpmn create mode 100644 tests/scripts/test_get_localtime.py diff --git a/crc/scripts/get_localtime.py b/crc/scripts/get_localtime.py new file mode 100644 index 00000000..b5c46f0e --- /dev/null +++ b/crc/scripts/get_localtime.py @@ -0,0 +1,19 @@ +from crc.scripts.script import Script +from crc.api.common import ApiError + + +class GetLocaltime(Script): + + def get_description(self): + return """Converts a UTC Datetime object into a Datetime object with a different timezone. + Defaults to US/Eastern""" + + def do_task_validate_only(self, task, study_id, workflow_id, **kwargs): + if len(kwargs): + return True + raise ApiError(code='missing_timestamp', + message='You must include a timestamp to convert.') + + def do_task(self, task, study_id, workflow_id, **kwargs): + pass + diff --git a/tests/data/get_localtime/get_localtime.bpmn b/tests/data/get_localtime/get_localtime.bpmn new file mode 100644 index 00000000..ff8d1d2f --- /dev/null +++ b/tests/data/get_localtime/get_localtime.bpmn @@ -0,0 +1,72 @@ + + + + + Flow_0lnc9x0 + + + + timestamp = email_model.timestamp +localtime = get_localtime(timestamp) + Flow_0gtgzcf + Flow_0k1hbif + + + + # Timestamp +{{ timestamp }} + + +# Localtime +{{ localtime }} + Flow_0k1hbif + Flow_0kgtoh1 + + + + Flow_0kgtoh1 + + + + This is my email + Flow_0lnc9x0 + Flow_0gtgzcf + email_model = email(subject='My Email Subject', recipients='user@example.com') + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/scripts/test_get_localtime.py b/tests/scripts/test_get_localtime.py new file mode 100644 index 00000000..460481fa --- /dev/null +++ b/tests/scripts/test_get_localtime.py @@ -0,0 +1,21 @@ +from tests.base_test import BaseTest +from crc.models.file import FileDataModel +from crc import session + + +class TestGetLocaltime(BaseTest): + + def test_get_localtime(self): + self.load_example_data() + # file_model = session.query(FileDataModel).first() + # test_time = file_model.date_created + + 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, {'timestamp': test_time}) + + # local_time = + + print('test_get_localtime') + From c2c79bd0144ad1be8c675c62b56f74d389484d21 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 14 Oct 2021 11:02:16 -0400 Subject: [PATCH 11/12] Convert UTC datetime object to a different timezone. JSON doesn't know about dates, so we have to return a string --- crc/scripts/get_localtime.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crc/scripts/get_localtime.py b/crc/scripts/get_localtime.py index b5c46f0e..83fab223 100644 --- a/crc/scripts/get_localtime.py +++ b/crc/scripts/get_localtime.py @@ -1,5 +1,8 @@ -from crc.scripts.script import Script from crc.api.common import ApiError +from crc.scripts.script import Script + +import dateparser +import pytz class GetLocaltime(Script): @@ -8,12 +11,24 @@ class GetLocaltime(Script): return """Converts a UTC Datetime object into a Datetime object with a different timezone. Defaults to US/Eastern""" - def do_task_validate_only(self, task, study_id, workflow_id, **kwargs): - if len(kwargs): + def do_task_validate_only(self, task, study_id, workflow_id, *args, **kwargs): + if 'timestamp' in kwargs: return True raise ApiError(code='missing_timestamp', message='You must include a timestamp to convert.') - def do_task(self, task, study_id, workflow_id, **kwargs): - pass + def do_task(self, task, study_id, workflow_id, *args, **kwargs): + if 'timestamp' in kwargs: + timestamp = kwargs['timestamp'] + if 'timezone' in kwargs: + timezone = kwargs['timezone'] + else: + timezone = 'US/Eastern' + parsed_timestamp = dateparser.parse(timestamp) + localtime = parsed_timestamp.astimezone(pytz.timezone(timezone)) + return str(localtime) + + else: + raise ApiError(code='missing_timestamp', + message='You must include a timestamp to convert.') From fc3e7f818324894934c7a1c4cbaca07326b3c888 Mon Sep 17 00:00:00 2001 From: mike cullerton Date: Thu, 14 Oct 2021 11:02:51 -0400 Subject: [PATCH 12/12] Simple test for get_localtime script --- tests/data/get_localtime/get_localtime.bpmn | 46 +++++++++++---------- tests/scripts/test_get_localtime.py | 12 ++---- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/tests/data/get_localtime/get_localtime.bpmn b/tests/data/get_localtime/get_localtime.bpmn index ff8d1d2f..43a136d5 100644 --- a/tests/data/get_localtime/get_localtime.bpmn +++ b/tests/data/get_localtime/get_localtime.bpmn @@ -5,23 +5,7 @@ Flow_0lnc9x0 - - timestamp = email_model.timestamp -localtime = get_localtime(timestamp) - Flow_0gtgzcf - Flow_0k1hbif - - - # Timestamp -{{ timestamp }} - - -# Localtime -{{ localtime }} - Flow_0k1hbif - Flow_0kgtoh1 - Flow_0kgtoh1 @@ -33,6 +17,24 @@ localtime = get_localtime(timestamp) Flow_0gtgzcf email_model = email(subject='My Email Subject', recipients='user@example.com') + + timestamp = email_model.timestamp +localtime = get_localtime(str(timestamp)) + Flow_0gtgzcf + Flow_0k1hbif + timestamp=email_model.timestamp +localtime = get_localtime(timestamp=timestamp) + + + # Timestamp +{{ timestamp }} + + +# Localtime +{{ localtime }} + Flow_0k1hbif + Flow_0kgtoh1 + @@ -55,18 +57,18 @@ localtime = get_localtime(timestamp) - - - - - - + + + + + + diff --git a/tests/scripts/test_get_localtime.py b/tests/scripts/test_get_localtime.py index 460481fa..7e5b489b 100644 --- a/tests/scripts/test_get_localtime.py +++ b/tests/scripts/test_get_localtime.py @@ -1,21 +1,17 @@ from tests.base_test import BaseTest -from crc.models.file import FileDataModel -from crc import session +from crc.scripts.get_localtime import GetLocaltime class TestGetLocaltime(BaseTest): def test_get_localtime(self): self.load_example_data() - # file_model = session.query(FileDataModel).first() - # test_time = file_model.date_created 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, {'timestamp': test_time}) - # local_time = - - print('test_get_localtime') + timestamp = task.data['timestamp'] + localtime = task.data['localtime'] + self.assertEqual(localtime, GetLocaltime().do_task(None, None, None, timestamp=timestamp))