-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2868 from uktrade/KLS-1795-inline-feedback
KLS-1795 add inline feedback component
- Loading branch information
Showing
36 changed files
with
651 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
import pickle | ||
from http.client import CREATED | ||
from urllib.parse import urlparse | ||
|
||
from directory_forms_api_client import actions | ||
from directory_forms_api_client.helpers import FormSessionMixin, Sender | ||
from django.conf import settings | ||
from django.core.cache import cache | ||
from django.http import HttpResponse, HttpResponseRedirect | ||
from django.shortcuts import redirect | ||
from django.template.loader import render_to_string | ||
from django.template.response import TemplateResponse | ||
|
@@ -14,6 +16,7 @@ | |
from django.views.generic import TemplateView | ||
from django.views.generic.edit import FormView | ||
from formtools.wizard.views import NamedUrlSessionWizardView | ||
from rest_framework.generics import GenericAPIView | ||
|
||
from contact import constants, forms as contact_forms, helpers, mixins as contact_mixins | ||
from core import mixins as core_mixins, snippet_slugs | ||
|
@@ -1130,3 +1133,52 @@ def get_context_data(self, **kwargs): | |
) | ||
context['privacy_url'] = PRIVACY_POLICY_URL__CONTACT_TRIAGE_FORMS_SPECIAL_PAGE | ||
return context | ||
|
||
|
||
class InlineFeedbackView(GenericAPIView): | ||
def post(self, request, *args, **kwargs): | ||
js_enabled = 'js_enabled' in request.query_params.keys() | ||
data = self.request.data.copy() | ||
|
||
# non-js for initial yes/no form where we use query params to pass the page_useful value | ||
if not js_enabled and 'page_useful' in request.query_params.keys(): | ||
data['page_useful'] = request.query_params['page_useful'] | ||
|
||
email_address = request.user.email if request.user.is_authenticated else '[email protected]' | ||
|
||
sender = Sender( | ||
email_address=email_address, | ||
country_code=None, | ||
) | ||
|
||
action = actions.SaveOnlyInDatabaseAction( | ||
full_name='NA', | ||
email_address=email_address, | ||
subject='NA', | ||
sender=sender, | ||
form_url=self.request.get_full_path(), | ||
) | ||
|
||
save_result = action.save(data) | ||
|
||
if js_enabled: | ||
response = HttpResponse() | ||
response.status_code = save_result.status_code | ||
return response | ||
else: | ||
# for non-js the user is redirected to the current page with some additional QS params that are used | ||
# when determining which elements should be displayed and navigates the user back to #inline-feedback | ||
if save_result.status_code == CREATED: | ||
qs = ( | ||
f"?page_useful={data['page_useful']}" | ||
if 'page_useful' in request.query_params.keys() | ||
else '?detailed_feedback_submitted=True' | ||
) | ||
response = HttpResponseRedirect(redirect_to=f"{data['current_url']}{qs}/#inline-feedback") | ||
else: | ||
response = HttpResponseRedirect( | ||
redirect_to=f"{data['current_url']}?submission_error=True/#inline-feedback" | ||
) | ||
|
||
response.status_code = 303 | ||
return response |
82 changes: 82 additions & 0 deletions
82
core/templates/components/inline_feedback/negative_feedback.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
<div> | ||
<div class="great-inline-feedback__submission-confirmation" | ||
id="negative-feedback-submision-confirmation" | ||
aria-live="polite" | ||
tabindex="-1"> | ||
<span role="img" class="fa fa-check-circle"></span> | ||
<h3>Thanks for letting us know</h3> | ||
</div> | ||
<div class="govuk-form-group"> | ||
<form method="post" | ||
action="{% url 'contact:contact-inline-feedback' %}?detailed_feedback_submitted=True" | ||
class="great-inline-feedback__detail-form" | ||
id="negative-feedback-form"> | ||
{% csrf_token %} | ||
<input type="hidden" name="current_url" value="{{ request.path }}"> | ||
<input type="hidden" name="page_useful" value="False"> | ||
<fieldset class="govuk-fieldset" aria-describedby="Why was this page useful?"> | ||
<legend class="great"> | ||
<span class="great-font-bold">Can you tell us more about your feedback?</span> | ||
</legend> | ||
<div class="govuk-checkboxes govuk-!-padding-top-4 govuk-!-padding-bottom-4" | ||
data-module="govuk-checkboxes"> | ||
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback"> | ||
<input type="checkbox" | ||
class="govuk-checkboxes__input" | ||
name="did_not_find_information" | ||
value="True" | ||
id="did_not_find_info"> | ||
<label for="did_not_find_info" class="govuk-label govuk-checkboxes__label"> | ||
I didn't find the information I needed | ||
</label> | ||
</div> | ||
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback"> | ||
<input type="checkbox" | ||
class="govuk-checkboxes__input" | ||
name="hard_to_find" | ||
value="True" | ||
id="hard_to_find"> | ||
<label for="hard_to_find" class="govuk-label govuk-checkboxes__label">It was hard to find</label> | ||
</div> | ||
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback"> | ||
<input type="checkbox" | ||
class="govuk-checkboxes__input" | ||
name="hard_to_understand" | ||
value="True" | ||
id="hard_to_understand"> | ||
<label for="hard_to_understand" class="govuk-label govuk-checkboxes__label">It was hard to understand</label> | ||
</div> | ||
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback"> | ||
<input type="checkbox" | ||
class="govuk-checkboxes__input" | ||
name="looked_broken" | ||
value="True" | ||
id="looked_broken"> | ||
<label for="looked_broken" class="govuk-label govuk-checkboxes__label">It looked broken</label> | ||
</div> | ||
</div> | ||
<div> | ||
<label for="more-detail" class="great"> | ||
<span class="great-font-bold">Is there anything else you can tell us?</span> | ||
</label> | ||
<div id="more-detail-hint"> | ||
<p>Do not share any personal or commercially sensitive information.</p> | ||
</div> | ||
<textarea class="govuk-textarea" | ||
id="more-detail" | ||
name="more_detail" | ||
rows="4" | ||
maxlength="1000" | ||
aria-describedby="more-detail-hint"></textarea> | ||
</div> | ||
</fieldset> | ||
<span class="great-inline-feedback-detail-form__submission_buttons govuk-!-padding-top-1"> | ||
<button type="submit" | ||
class="primary-button small-button" | ||
id="send-feedback-no" | ||
value="no">Send feedback</button> | ||
<a href={{ request.path }}>Cancel</a> | ||
</span> | ||
</form> | ||
</div> | ||
</div> |
81 changes: 81 additions & 0 deletions
81
core/templates/components/inline_feedback/positive_feedback.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
<div> | ||
<div class="great-inline-feedback__submission-confirmation" | ||
aria-live="polite" | ||
id="positive-feedback-submision-confirmation" | ||
tabindex="-1"> | ||
<span role="img" class="fa fa-check-circle"></span> | ||
<h3>Thanks for letting us know</h3> | ||
</div> | ||
<div class="govuk-form-group"> | ||
<form method="post" | ||
action="{% url 'contact:contact-inline-feedback' %}?detailed_feedback_submitted=True" | ||
class="great-inline-feedback__detail-form" | ||
id="positive-feedback-form"> | ||
{% csrf_token %} | ||
<input type="hidden" name="current_url" value="{{ request.path }}"> | ||
<input type="hidden" name="page_useful" value="True"> | ||
<fieldset class="govuk-fieldset" aria-describedby="Why was this page useful?"> | ||
<legend class="great"> | ||
<span class="great-font-bold">Can you tell us why this page was useful?</span> | ||
</legend> | ||
<div class="govuk-checkboxes govuk-!-padding-top-4 govuk-!-padding-bottom-4" | ||
data-module="govuk-checkboxes"> | ||
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback"> | ||
<input type="checkbox" | ||
class="govuk-checkboxes__input" | ||
name="found_information_needed" | ||
value="True" | ||
id="found-info"> | ||
<label for="found-info" class="govuk-label govuk-checkboxes__label">I found the information I needed</label> | ||
</div> | ||
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback"> | ||
<input type="checkbox" | ||
class="govuk-checkboxes__input" | ||
name="easily_found" | ||
value="True" | ||
id="easily-found"> | ||
<label for="easily-found" class="govuk-label govuk-checkboxes__label">It was easy to find</label> | ||
</div> | ||
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback"> | ||
<input type="checkbox" | ||
class="govuk-checkboxes__input" | ||
name="easily_understood" | ||
value="True" | ||
id="easily-understood"> | ||
<label for="easily-understood" class="govuk-label govuk-checkboxes__label">It was easy to understand</label> | ||
</div> | ||
<div class="govuk-checkboxes__item govuk-!-margin-bottom-2 great-checkbox--inline-feedback"> | ||
<input type="checkbox" | ||
class="govuk-checkboxes__input" | ||
name="will_use_information_again" | ||
value="True" | ||
id="will-use-information-again"> | ||
<label for="will-use-information-again" | ||
class="govuk-label govuk-checkboxes__label">I will use this information again</label> | ||
</div> | ||
</div> | ||
<div> | ||
<label for="more-detail" class="great"> | ||
<span class="great-font-bold">Is there anything else you can tell us?</span> | ||
</label> | ||
<div id="more-detail-hint-"> | ||
<p>Do not share any personal or commercially sensitive information.</p> | ||
</div> | ||
<textarea class="govuk-textarea" | ||
id="more-detail" | ||
name="more_detail" | ||
rows="4" | ||
maxlength="1000" | ||
aria-describedby="more-detail-hint"></textarea> | ||
</div> | ||
</fieldset> | ||
<span class="great-inline-feedback-detail-form__submission_buttons govuk-!-padding-top-1"> | ||
<button type="submit" | ||
class="primary-button small-button" | ||
id="send-feedback-yes" | ||
value="yes">Send feedback</button> | ||
<a href={{ request.path }}>Cancel</a> | ||
</span> | ||
</form> | ||
</div> | ||
</div> |
137 changes: 137 additions & 0 deletions
137
core/templates/components/inline_feedback/was_page_useful.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
{% load show_feedback from content_tags %} | ||
{% load get_inline_feedback_visibility from content_tags %} | ||
{% show_feedback request.get_full_path as show_feedback %} | ||
{% if show_feedback %} | ||
<div class="great-full-width govuk-!-padding-top-6 govuk-!-padding-bottom-6 great-inline-feedback__container"> | ||
<div class="govuk-grid-row great-container"> | ||
<div class="govuk-grid-column-two-thirds govuk-!-padding-0" | ||
id="inline-feedback"> | ||
{% get_inline_feedback_visibility request.get_full_path as feedback_visibility %} | ||
<div id="submission-error" | ||
aria-live="polite" | ||
tabindex="-1" | ||
class="great-inline-feedback__submission-error {% if not feedback_visibility.show_submission_error %}great-hidden{% endif %}"> | ||
<span role="img" class="fa fa-exclamation-circle"></span> | ||
<h3>Something went wrong. Please try again.</h3> | ||
</div> | ||
<span id="page-useful" | ||
class="{% if not feedback_visibility.show_page_useful %} great-hidden {% endif %}"> | ||
<form method="post" | ||
class="great-inline-feedback__page_useful_form" | ||
id="page-useful-form"> | ||
<h3 class="govuk-heading-xs">Was this page useful?</h3> | ||
{% csrf_token %} | ||
<input type="hidden" name="current_url" value="{{ request.path }}"> | ||
<button type="submit" | ||
class="secondary-button small-button" | ||
id="page-useful-yes" | ||
formaction="{% url 'contact:contact-inline-feedback' %}?page_useful=True">Yes</button> | ||
<button type="submit" | ||
class="secondary-button small-button" | ||
id="page-useful-no" | ||
formaction="{% url 'contact:contact-inline-feedback' %}?page_useful=False">No</button> | ||
</form> | ||
</span> | ||
<span id="positive-feedback" | ||
class="{% if not feedback_visibility.show_positive_feedback %} great-hidden {% endif %}"> | ||
{% include 'components/inline_feedback/positive_feedback.html' %} | ||
</span> | ||
<span id="negative-feedback" | ||
class="{% if not feedback_visibility.show_negative_feedback %} great-hidden {% endif %}"> | ||
{% include 'components/inline_feedback/negative_feedback.html' %} | ||
</span> | ||
<div id="detailed-feedback-received" | ||
aria-live="polite" | ||
tabindex="-1" | ||
class="great-inline-feedback__submission-confirmation {% if not feedback_visibility.show_detailed_feedback_received %}great-hidden{% endif %}"> | ||
<span role="img" class="fa fa-check-circle"></span> | ||
<h2>Thanks for your feedback</h2> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
{% block body_js %} | ||
<script> | ||
const currentPage = window.location.pathname | ||
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value | ||
|
||
const postFeedback = async function (body){ | ||
const submitAPIEndpoint = '/contact/inline-feedback' | ||
return await fetch(`${submitAPIEndpoint}?js_enabled=True`, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': "application/json", | ||
'X-CSRFToken': csrfToken | ||
}, | ||
body: JSON.stringify(body) | ||
}) | ||
} | ||
|
||
const hideFeedbackError = function () { | ||
document.getElementById('submission-error').classList.add('great-hidden') | ||
} | ||
|
||
const handleFeedbackError = function() { | ||
document.getElementById('submission-error').classList.remove('great-hidden') | ||
document.getElementById('submission-error').focus() | ||
} | ||
|
||
const handleFeedbackSuccess = function(originalElementID, successElementID, successMessageID) { | ||
document.getElementById(originalElementID).classList.toggle('great-hidden') | ||
document.getElementById(successElementID).classList.toggle('great-hidden') | ||
document.getElementById(successMessageID).focus() | ||
} | ||
|
||
document.getElementById('page-useful-form').addEventListener('submit', function(event){ | ||
event.preventDefault(); | ||
}) | ||
|
||
const pageUsefulButtons = [{'id':'page-useful-yes', 'followUpFormID': 'positive-feedback', 'successMessageID': 'positive-feedback-submision-confirmation'}, {'id':'page-useful-no', 'followUpFormID': 'negative-feedback', 'successMessageID': 'negative-feedback-submision-confirmation'}] | ||
|
||
pageUsefulButtons.forEach((button)=>{ | ||
document.getElementById(button.id).addEventListener('click', async function(event){ | ||
try{ | ||
hideFeedbackError() | ||
await postFeedback({ | ||
'current_url': currentPage, | ||
'page_title': document.title, | ||
'page_useful': `${button.id == 'page-useful-yes'}`, | ||
}) | ||
handleFeedbackSuccess('page-useful', button.followUpFormID, button.successMessageID) | ||
} catch (e) { | ||
handleFeedbackError() | ||
} | ||
}) | ||
}) | ||
|
||
const detailedFeedbackIDs = [{'containerID': 'positive-feedback', 'formID': 'positive-feedback-form'}, {'containerID': 'negative-feedback', 'formID': 'negative-feedback-form'}] | ||
|
||
detailedFeedbackIDs.forEach((feedbackType)=>{ | ||
document.getElementById(feedbackType.formID).addEventListener('submit', async function(event){ | ||
event.preventDefault(); | ||
const form = event.target | ||
const formData = new FormData(form) | ||
|
||
const formDataObject = {}; | ||
formData.forEach((value, key)=>{ | ||
if (key != 'csrfmiddlewaretoken') formDataObject[key] = value; | ||
}); | ||
|
||
try { | ||
hideFeedbackError() | ||
await postFeedback({ | ||
'current_url': currentPage, | ||
'page_title': document.title, | ||
'page_useful': feedbackType.containerID == 'positive-feedback', | ||
...formDataObject, | ||
}) | ||
handleFeedbackSuccess(feedbackType.containerID, 'detailed-feedback-received', 'detailed-feedback-received') | ||
} catch (e) { | ||
handleFeedbackError() | ||
} | ||
}) | ||
}) | ||
|
||
</script> | ||
{% endblock %} | ||
{% endif %} |
Oops, something went wrong.