Merge pull request #323 from dod-ccpo/internal-comments

Hook up internal request comments
This commit is contained in:
patricksmithdds 2018-09-24 17:53:06 -04:00 committed by GitHub
commit 6ac19d315d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 94 additions and 58 deletions

View File

@ -225,8 +225,8 @@ class Requests(object):
return Requests._add_review(user, request, review_data) return Requests._add_review(user, request, review_data)
@classmethod @classmethod
def update_internal_comments(cls, user, request, comment_text): def add_internal_comment(cls, user, request, comment_text):
Authorization.check_can_approve_request(user) Authorization.check_can_approve_request(user)
request.internal_comments = RequestInternalComment(text=comment_text, user=user) comment = RequestInternalComment(request=request, text=comment_text, user=user)
request = RequestsQuery.add_and_commit(request) RequestsQuery.add_and_commit(comment)
return request return request

View File

@ -1,5 +1,5 @@
from wtforms.fields import TextAreaField from wtforms.fields import TextAreaField
from wtforms.validators import Optional from wtforms.validators import InputRequired
from .forms import ValidatedForm from .forms import ValidatedForm
@ -7,6 +7,7 @@ from .forms import ValidatedForm
class InternalCommentForm(ValidatedForm): class InternalCommentForm(ValidatedForm):
text = TextAreaField( text = TextAreaField(
"CCPO Internal Notes", "CCPO Internal Notes",
description="Add comments or notes for internal CCPO reference and follow-up here.<strong>These comments <em>will not</em> be visible to the person making the JEDI request.</strong>", default="",
validators=[Optional()], description="Add comments or notes for internal CCPO reference and follow-up here. <strong>These comments <em>will not</em> be visible to the person making the JEDI request.</strong>",
validators=[InputRequired()],
) )

View File

@ -45,7 +45,7 @@ class Request(Base, mixins.TimestampsMixin):
"RequestRevision", back_populates="request", order_by="RequestRevision.sequence" "RequestRevision", back_populates="request", order_by="RequestRevision.sequence"
) )
internal_comments = relationship("RequestInternalComment", uselist=False) internal_comments = relationship("RequestInternalComment")
@property @property
def latest_revision(self): def latest_revision(self):
@ -167,10 +167,6 @@ class Request(Base, mixins.TimestampsMixin):
def reviews(self): def reviews(self):
return [status.review for status in self.status_events if status.review] return [status.review for status in self.status_events if status.review]
@property
def internal_comments_text(self):
return self.internal_comments.text if self.internal_comments else ""
@property @property
def is_pending_financial_verification(self): def is_pending_financial_verification(self):
return self.status == RequestStatus.PENDING_FINANCIAL_VERIFICATION return self.status == RequestStatus.PENDING_FINANCIAL_VERIFICATION

View File

@ -14,3 +14,4 @@ class RequestInternalComment(Base, mixins.TimestampsMixin):
user = relationship("User") user = relationship("User")
request_id = Column(ForeignKey("requests.id", ondelete="CASCADE"), nullable=False) request_id = Column(ForeignKey("requests.id", ondelete="CASCADE"), nullable=False)
request = relationship("Request")

View File

@ -14,14 +14,12 @@ from atst.domain.exceptions import NotFoundError
from atst.forms.ccpo_review import CCPOReviewForm from atst.forms.ccpo_review import CCPOReviewForm
from atst.forms.internal_comment import InternalCommentForm from atst.forms.internal_comment import InternalCommentForm
from datetime import date
def map_ccpo_authorizing(user): def map_ccpo_authorizing(user):
return {"fname_ccpo": user.first_name, "lname_ccpo": user.last_name} return {"fname_ccpo": user.first_name, "lname_ccpo": user.last_name}
def render_approval(request, form=None): def render_approval(request, form=None, internal_comment_form=None):
data = request.body data = request.body
if request.has_financial_data: if request.has_financial_data:
data["task_order"] = request.task_order.to_dictionary() data["task_order"] = request.task_order.to_dictionary()
@ -30,21 +28,8 @@ def render_approval(request, form=None):
mo_data = map_ccpo_authorizing(g.current_user) mo_data = map_ccpo_authorizing(g.current_user)
form = CCPOReviewForm(data=mo_data) form = CCPOReviewForm(data=mo_data)
internal_comment_form = InternalCommentForm(text=request.internal_comments_text) if not internal_comment_form:
internal_comment_form = InternalCommentForm()
# Dummy internal comments
comments = [
{
"time_created": date(2018, 9, 18),
"full_name_commenter": "Darth Vader",
"message": "We'll have no more of this Obi-Wan Kenobi jibberish...and don't talk to me about your mission, either. You're fortunate he doesn't blast you into a million pieces right here. ",
},
{
"time_created": date(2018, 9, 20),
"full_name_commenter": "Grand Moff Tarkin",
"message": "We'll have no more of this Obi-Wan Kenobi jibberish...and don't talk to me about your mission, either. You're fortunate he doesn't blast you into a million pieces right here. ",
},
]
return render_template( return render_template(
"requests/approval.html", "requests/approval.html",
@ -52,9 +37,9 @@ def render_approval(request, form=None):
reviews=list(reversed(request.reviews)), reviews=list(reversed(request.reviews)),
request=request, request=request,
current_status=request.status.value, current_status=request.status.value,
f=form or CCPOReviewForm(), review_form=form or CCPOReviewForm(),
internal_comment_form=internal_comment_form, internal_comment_form=internal_comment_form,
comments=comments, comments=request.internal_comments,
) )
@ -101,10 +86,12 @@ def task_order_pdf_download(request_id):
@requests_bp.route("/requests/internal_comments/<string:request_id>", methods=["POST"]) @requests_bp.route("/requests/internal_comments/<string:request_id>", methods=["POST"])
def create_internal_comment(request_id): def create_internal_comment(request_id):
# form = InternalCommentForm(http_request.form) form = InternalCommentForm(http_request.form)
# if form.validate(): request = Requests.get(g.current_user, request_id)
# request = Requests.get(g.current_user, request_id) if form.validate():
# Requests.update_internal_comments(g.current_user, request, form.data["text"]) Requests.add_internal_comment(g.current_user, request, form.data.get("text"))
return redirect( return redirect(
url_for("requests.approval", request_id=request_id, _anchor="ccpo-notes") url_for("requests.approval", request_id=request_id, _anchor="ccpo-notes")
) )
else:
return render_approval(request, internal_comment_form=form)

View File

@ -8,7 +8,7 @@
<article class='col col--grow request-approval'> <article class='col col--grow request-approval'>
{% if f.errors %} {% if review_form.errors or internal_comment_form.errors %}
{{ Alert('There were some errors', {{ Alert('There were some errors',
message="<p>Please see below.</p>", message="<p>Please see below.</p>",
level='error' level='error'
@ -32,11 +32,6 @@
</section> </section>
{{ Alert('Comments and comment form are fake!',
message="<p>Please note, the comments and comment form below are just mocked out. Submitting it will do nothing. These will be hooked up to real functionality shortly. </p><p><strong>Engineer: please remove this alert when you do your thing.</strong></p>",
level='warning'
) }}
<section class='internal-notes' id='ccpo-notes'> <section class='internal-notes' id='ccpo-notes'>
<form method="POST" action="{{ url_for('requests.create_internal_comment', request_id=request.id) }}"> <form method="POST" action="{{ url_for('requests.create_internal_comment', request_id=request.id) }}">
<div class='panel'> <div class='panel'>
@ -51,8 +46,8 @@
<li> <li>
<article class='comment-log__log-item'> <article class='comment-log__log-item'>
<div> <div>
<h3 class='comment-log__log-item__header'>{{ comment.full_name_commenter }}</h3> <h3 class='comment-log__log-item__header'>{{ comment.user.full_name }}</h3>
<p>{{ comment.message }}</p> <p>{{ comment.text }}</p>
</div> </div>
{% set timestamp=comment.time_created | formattedDate("%Y-%m-%d %H:%M:%S %Z") %} {% set timestamp=comment.time_created | formattedDate("%Y-%m-%d %H:%M:%S %Z") %}
<footer class='comment-log__log-item__timestamp'> <footer class='comment-log__log-item__timestamp'>
@ -88,7 +83,7 @@
<section class='request-approval__review'> <section class='request-approval__review'>
<form method="POST" action="{{ url_for("requests.submit_approval", request_id=request.id) }}" autocomplete="off"> <form method="POST" action="{{ url_for("requests.submit_approval", request_id=request.id) }}" autocomplete="off">
{{ f.csrf_token }} {{ review_form.csrf_token }}
<ccpo-approval inline-template> <ccpo-approval inline-template>
<div> <div>
@ -165,7 +160,7 @@
<h3>Message to Requestor <span class='subtitle'>(optional)</span></h3> <h3>Message to Requestor <span class='subtitle'>(optional)</span></h3>
<div v-if='approving' key='approving' v-cloak> <div v-if='approving' key='approving' v-cloak>
{{ TextInput( {{ TextInput(
f.comment, review_form.comment,
label='Approval comments or notes', label='Approval comments or notes',
description='Provide any comments or notes regarding the approval of this request. <strong>This message will be shared with the person making the JEDI request.</strong>.', description='Provide any comments or notes regarding the approval of this request. <strong>This message will be shared with the person making the JEDI request.</strong>.',
paragraph=True, paragraph=True,
@ -175,7 +170,7 @@
<div v-else key='denying' v-cloak> <div v-else key='denying' v-cloak>
{{ TextInput( {{ TextInput(
f.comment, review_form.comment,
label='Revision instructions or notes', label='Revision instructions or notes',
paragraph=True, paragraph=True,
noMaxWidth=True noMaxWidth=True
@ -194,21 +189,21 @@
<div class='form-row'> <div class='form-row'>
<div class='form-col form-col--half'> <div class='form-col form-col--half'>
{{ TextInput(f.fname_mao, placeholder="First name of mission authorizing official") }} {{ TextInput(review_form.fname_mao, placeholder="First name of mission authorizing official") }}
</div> </div>
<div class='form-col form-col--half'> <div class='form-col form-col--half'>
{{ TextInput(f.lname_mao, placeholder="Last name of mission authorizing official") }} {{ TextInput(review_form.lname_mao, placeholder="Last name of mission authorizing official") }}
</div> </div>
</div> </div>
<div class='form-row'> <div class='form-row'>
<div class='form-col form-col--half'> <div class='form-col form-col--half'>
{{ TextInput(f.email_mao, placeholder="name@mail.mil", validation='email') }} {{ TextInput(review_form.email_mao, placeholder="name@mail.mil", validation='email') }}
</div> </div>
<div class='form-col form-col--half'> <div class='form-col form-col--half'>
{{ TextInput(f.phone_mao, placeholder="(123) 456-7890", validation='usPhone') }} {{ TextInput(review_form.phone_mao, placeholder="(123) 456-7890", validation='usPhone') }}
</div> </div>
</div> </div>
@ -218,11 +213,11 @@
<div class='form-row'> <div class='form-row'>
<div class='form-col form-col--half'> <div class='form-col form-col--half'>
{{ TextInput(f.fname_ccpo, placeholder="First name of CCPO authorizing official") }} {{ TextInput(review_form.fname_ccpo, placeholder="First name of CCPO authorizing official") }}
</div> </div>
<div class='form-col form-col--half'> <div class='form-col form-col--half'>
{{ TextInput(f.lname_ccpo, placeholder="Last name of CCPO authorizing official") }} {{ TextInput(review_form.lname_ccpo, placeholder="Last name of CCPO authorizing official") }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -223,10 +223,13 @@ def test_request_changes_to_financial_verification_info():
assert current_review.fname_mao == review_data["fname_mao"] assert current_review.fname_mao == review_data["fname_mao"]
def test_update_internal_comments(): def test_add_internal_comment():
request = RequestFactory.create() request = RequestFactory.create()
ccpo = UserFactory.from_atat_role("ccpo") ccpo = UserFactory.from_atat_role("ccpo")
request = Requests.update_internal_comments(ccpo, request, "this is my comment") assert len(request.internal_comments) == 0
assert request.internal_comments.text == "this is my comment" request = Requests.add_internal_comment(ccpo, request, "this is my comment")
assert len(request.internal_comments) == 1
assert request.internal_comments[0].text == "this is my comment"

View File

@ -108,3 +108,56 @@ def test_can_submit_request_denial(client, user_session):
) )
assert response.status_code == 302 assert response.status_code == 302
assert request.status == RequestStatus.CHANGES_REQUESTED assert request.status == RequestStatus.CHANGES_REQUESTED
def test_ccpo_user_can_comment_on_request(client, user_session):
user = UserFactory.from_atat_role("ccpo")
user_session(user)
request = RequestFactory.create_with_status(
status=RequestStatus.PENDING_CCPO_ACCEPTANCE
)
assert len(request.internal_comments) == 0
comment_text = "This is the greatest request in the history of requests"
comment_form_data = {"text": comment_text}
response = client.post(
url_for("requests.create_internal_comment", request_id=request.id),
data=comment_form_data,
)
assert response.status_code == 302
assert len(request.internal_comments) == 1
assert request.internal_comments[0].text == comment_text
def test_comment_text_is_required(client, user_session):
user = UserFactory.from_atat_role("ccpo")
user_session(user)
request = RequestFactory.create_with_status(
status=RequestStatus.PENDING_CCPO_ACCEPTANCE
)
assert len(request.internal_comments) == 0
comment_form_data = {"text": ""}
response = client.post(
url_for("requests.create_internal_comment", request_id=request.id),
data=comment_form_data,
)
assert response.status_code == 200
assert len(request.internal_comments) == 0
def test_other_user_cannot_comment_on_request(client, user_session):
user = UserFactory.create()
user_session(user)
request = RequestFactory.create_with_status(
status=RequestStatus.PENDING_CCPO_ACCEPTANCE
)
comment_text = "What is this even"
comment_form_data = {"text": comment_text}
response = client.post(
url_for("requests.create_internal_comment", request_id=request.id),
data=comment_form_data,
)
assert response.status_code == 404