diff --git a/atst/domain/requests/requests.py b/atst/domain/requests/requests.py
index 0c966ed8..86e2908d 100644
--- a/atst/domain/requests/requests.py
+++ b/atst/domain/requests/requests.py
@@ -6,9 +6,11 @@ from atst.models.request_status_event import RequestStatusEvent, RequestStatus
from atst.models.request_review import RequestReview
from atst.models.request_internal_comment import RequestInternalComment
from atst.utils import deep_merge
+from atst.queue import queue
from .query import RequestsQuery
from .authorization import RequestsAuthorization
+from .status_event_handler import RequestStatusEventHandler
def create_revision_from_request_body(body):
@@ -95,11 +97,17 @@ class Requests(object):
@classmethod
def set_status(cls, request, status: RequestStatus):
+ old_status = request.status
status_event = RequestStatusEvent(
new_status=status, revision=request.latest_revision
)
request.status_events.append(status_event)
- return RequestsQuery.add_and_commit(request)
+ updated_request = RequestsQuery.add_and_commit(request)
+ RequestStatusEventHandler(queue).handle_status_change(
+ updated_request, old_status, status
+ )
+
+ return updated_request
@classmethod
def should_auto_approve(cls, request):
diff --git a/atst/domain/requests/status_event_handler.py b/atst/domain/requests/status_event_handler.py
new file mode 100644
index 00000000..a840642c
--- /dev/null
+++ b/atst/domain/requests/status_event_handler.py
@@ -0,0 +1,41 @@
+from flask import render_template
+
+from atst.models.request_status_event import RequestStatus
+
+
+class RequestStatusEventHandler(object):
+ def __init__(self, queue):
+ self.queue = queue
+
+ def handle_status_change(self, request, old_status, new_status):
+ handler = self._get_handler(old_status, new_status)
+ if handler:
+ handler(request)
+
+ def _get_handler(self, old_status, new_status):
+ return {
+ (
+ RequestStatus.PENDING_CCPO_ACCEPTANCE,
+ RequestStatus.PENDING_FINANCIAL_VERIFICATION,
+ ): self._send_email,
+ (
+ RequestStatus.PENDING_CCPO_ACCEPTANCE,
+ RequestStatus.CHANGES_REQUESTED,
+ ): self._send_email,
+ (
+ RequestStatus.PENDING_CCPO_APPROVAL,
+ RequestStatus.CHANGES_REQUESTED_TO_FINVER,
+ ): self._send_email,
+ (
+ RequestStatus.PENDING_CCPO_APPROVAL,
+ RequestStatus.APPROVED,
+ ): self._send_email,
+ }.get((old_status, new_status))
+
+ def _send_email(self, request):
+ email_body = render_template(
+ "emails/request_status_change.txt", request=request
+ )
+ self.queue.send_mail(
+ [request.creator.email], "Your JEDI request status has changed", email_body
+ )
diff --git a/atst/queue.py b/atst/queue.py
index e036a642..befe45b0 100644
--- a/atst/queue.py
+++ b/atst/queue.py
@@ -27,8 +27,8 @@ class ATSTQueue(RQ):
# pylint: disable=pointless-string-statement
"""Instance methods to queue up application-specific jobs."""
- def send_mail(self, to, subject, body):
- self._queue_job(ATSTQueue._send_mail, to, subject, body)
+ def send_mail(self, recipients, subject, body):
+ self._queue_job(ATSTQueue._send_mail, recipients, subject, body)
# pylint: disable=pointless-string-statement
"""Class methods to actually perform the work.
@@ -38,8 +38,8 @@ class ATSTQueue(RQ):
"""
@classmethod
- def _send_mail(self, to, subject, body):
- app.mailer.send(to, subject, body)
+ def _send_mail(self, recipients, subject, body):
+ app.mailer.send(recipients, subject, body)
queue = ATSTQueue()
diff --git a/templates/emails/request_status_change.txt b/templates/emails/request_status_change.txt
new file mode 100644
index 00000000..75bb7a0b
--- /dev/null
+++ b/templates/emails/request_status_change.txt
@@ -0,0 +1,5 @@
+
Your JEDI request status has changed
+
+The status of your JEDI Cloud request - {{ request.name }} - was recently updated. Log in to see whether this change requires an action or response from you.
+
+{{ url_for('requests.view_request_details', request_id=request.id, _external=True) }}
diff --git a/tests/conftest.py b/tests/conftest.py
index c1821b96..67912c1d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -137,8 +137,7 @@ def extended_financial_verification_data(pdf_upload):
}
-@pytest.fixture(scope="function")
+@pytest.fixture(scope="function", autouse=True)
def queue():
- _queue = atst_queue
- yield _queue
- _queue.get_queue().empty()
+ yield atst_queue
+ atst_queue.get_queue().empty()
diff --git a/tests/domain/test_requests.py b/tests/domain/test_requests.py
index 90e3655d..72971568 100644
--- a/tests/domain/test_requests.py
+++ b/tests/domain/test_requests.py
@@ -6,13 +6,11 @@ from atst.domain.requests import Requests
from atst.domain.requests.authorization import RequestsAuthorization
from atst.models.request import Request
from atst.models.request_status_event import RequestStatus
-from atst.models.task_order import Source as TaskOrderSource
from tests.factories import (
RequestFactory,
UserFactory,
RequestStatusEventFactory,
- TaskOrderFactory,
RequestRevisionFactory,
RequestReviewFactory,
)
@@ -222,3 +220,37 @@ def test_random_user_cannot_view_request():
request = RequestFactory.create()
assert not RequestsAuthorization(user, request).can_view
+
+
+def test_pending_finver_triggers_notification(queue):
+ request = RequestFactory.create()
+ request = Requests.set_status(request, RequestStatus.PENDING_CCPO_ACCEPTANCE)
+ request = Requests.set_status(request, RequestStatus.PENDING_FINANCIAL_VERIFICATION)
+ assert len(queue.get_queue()) == 1
+
+
+def test_changes_requested_triggers_notification(queue):
+ request = RequestFactory.create()
+ request = Requests.set_status(request, RequestStatus.PENDING_CCPO_ACCEPTANCE)
+ request = Requests.set_status(request, RequestStatus.CHANGES_REQUESTED)
+ assert len(queue.get_queue()) == 1
+
+
+def test_changes_requested_to_finver_triggers_notification(queue):
+ request = RequestFactory.create()
+ request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
+ request = Requests.set_status(request, RequestStatus.CHANGES_REQUESTED_TO_FINVER)
+ assert len(queue.get_queue()) == 1
+
+
+def test_approval_triggers_notification(queue):
+ request = RequestFactory.create()
+ request = Requests.set_status(request, RequestStatus.PENDING_CCPO_APPROVAL)
+ request = Requests.set_status(request, RequestStatus.APPROVED)
+ assert len(queue.get_queue()) == 1
+
+
+def test_submitted_does_not_trigger_notification(queue):
+ request = RequestFactory.create()
+ request = Requests.set_status(request, RequestStatus.SUBMITTED)
+ assert len(queue.get_queue()) == 0
diff --git a/tests/routes/test_request_new.py b/tests/routes/test_request_new.py
index d64d21e0..81d195a8 100644
--- a/tests/routes/test_request_new.py
+++ b/tests/routes/test_request_new.py
@@ -239,7 +239,9 @@ def test_displays_ccpo_review_comment(user_session, client):
request = RequestFactory.create(creator=creator)
request = Requests.set_status(request, RequestStatus.CHANGES_REQUESTED)
review_comment = "add all of the correct info, instead of the incorrect info"
- RequestReviewFactory.create(reviewer=ccpo, comment=review_comment, status=request.status_events[-1])
+ RequestReviewFactory.create(
+ reviewer=ccpo, comment=review_comment, status=request.status_events[-1]
+ )
response = client.get("/requests/new/1/{}".format(request.id))
body = response.data.decode()
assert review_comment in body