diff --git a/app/main/forms.py b/app/main/forms.py
index 04d9e4cc7..48fe081f5 100644
--- a/app/main/forms.py
+++ b/app/main/forms.py
@@ -306,3 +306,10 @@ class CreateKeyForm(Form):
def validate_key_name(self, key_name):
if key_name.data.lower() in self.existing_key_names:
raise ValidationError('A key with this name already exists')
+
+
+class Feedback(Form):
+
+ name = StringField('Name')
+ email_address = StringField('Email address')
+ feedback = TextAreaField(u'', validators=[DataRequired(message="Can’t be empty")])
diff --git a/app/main/views/index.py b/app/main/views/index.py
index d81bcdc23..a68eac7c2 100644
--- a/app/main/views/index.py
+++ b/app/main/views/index.py
@@ -1,8 +1,11 @@
import markdown
import os
-from flask import render_template, url_for, redirect, Markup
+import requests
+import json
+from flask import (render_template, url_for, redirect, Markup, flash, current_app, abort)
from app.main import main
from flask_login import login_required
+from app.main.forms import Feedback
from flask.ext.login import current_user
from mdx_gfm import GithubFlavoredMarkdownExtension
@@ -41,6 +44,40 @@ def terms():
return render_template('views/terms-of-use.html')
+@main.route('/feedback', methods=['GET', 'POST'])
+def feedback():
+ form = Feedback()
+ if form.validate_on_submit():
+ data = {
+ 'person_email': current_app.config.get('DESKPRO_PERSON_EMAIL'),
+ 'agent_team_id': current_app.config.get('DESKPRO_TEAM_ID'),
+ 'subject': 'Notify feedback',
+ 'message': '{}\n{}\n{}'.format(
+ form.name.data,
+ form.email_address.data,
+ form.feedback.data)
+ }
+ headers = {
+ "X-DeskPRO-API-Key": current_app.config.get('DESKPRO_API_KEY'),
+ 'Content-Type': "application/x-www-form-urlencoded"
+ }
+ resp = requests.post(
+ current_app.config.get('DESKPRO_API_HOST') + '/api/tickets',
+ data=data,
+ headers=headers)
+ if resp.status_code != 201:
+ current_app.logger.error(
+ "Deskpro create ticket request failed with {} '{}'".format(
+ resp.status_code,
+ resp.json())
+ )
+ abort(500, "Feedback submission failed")
+ flash("Your feedback has been submitted")
+ return redirect(url_for('.feedback'))
+
+ return render_template('views/feedback.html', form=form)
+
+
@main.route('/documentation')
def documentation():
curr_dir = os.path.dirname(os.path.realpath(__file__))
diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html
index 168525842..9c5461dca 100644
--- a/app/templates/admin_template.html
+++ b/app/templates/admin_template.html
@@ -78,7 +78,7 @@
diff --git a/app/templates/views/feedback.html b/app/templates/views/feedback.html
new file mode 100644
index 000000000..3ee5f7401
--- /dev/null
+++ b/app/templates/views/feedback.html
@@ -0,0 +1,28 @@
+{% extends "withoutnav_template.html" %}
+{% from "components/textbox.html" import textbox %}
+{% from "components/page-footer.html" import page_footer %}
+
+{% block page_title %}
+ Feedback – GOV.UK Notify
+{% endblock %}
+
+{% block maincolumn_content %}
+
+
+ Give feedback
+
+
+
+
What went wrong, if anything? What went well? How could we improve this service?
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/config.py b/config.py
index b8971c7f8..ed8cbd7da 100644
--- a/config.py
+++ b/config.py
@@ -36,6 +36,10 @@ class Config(object):
TOKEN_MAX_AGE_SECONDS = 3600
WTF_CSRF_ENABLED = True
CSV_UPLOAD_BUCKET_NAME = 'local-notifications-csv-upload'
+ DESKPRO_API_HOST = os.environ['DESKPRO_API_HOST']
+ DESKPRO_API_KEY = os.environ['DESKPRO_API_KEY']
+ DESKPRO_PERSON_EMAIL = os.environ['DESKPRO_PERSON_EMAIL']
+ DESKPRO_TEAM_ID = os.environ['DESKPRO_TEAM_ID']
EMAIL_DOMAIN_REGEXES = [
"gov\.uk",
diff --git a/environment_test.sh b/environment_test.sh
index 57f89ae58..e3f02a1a7 100644
--- a/environment_test.sh
+++ b/environment_test.sh
@@ -4,3 +4,7 @@ export ADMIN_CLIENT_USER_NAME='dev-notify-admin'
export API_HOST_NAME='http://localhost:6311'
export DANGEROUS_SALT='dev-notify-salt'
export SECRET_KEY='dev-notify-secret-key'
+export DESKPRO_API_HOST=""
+export DESKPRO_API_KEY=""
+export DESKPRO_PERSON_EMAIL=""
+export DESKPRO_TEAM_ID=""
diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py
index 7cfbd8142..7c45e5d5d 100644
--- a/tests/app/main/views/test_accept_invite.py
+++ b/tests/app/main/views/test_accept_invite.py
@@ -1,6 +1,7 @@
from flask import url_for
from bs4 import BeautifulSoup
+from unittest.mock import ANY
import app
@@ -255,7 +256,6 @@ def test_new_user_accept_invite_completes_new_registration_redirects_to_verify(a
assert response.status_code == 302
assert response.location == expected_redirect_location
- from unittest.mock import ANY
mock_send_verify_code.assert_called_once_with(ANY, 'sms', data['mobile_number'])
mock_register_user.assert_called_with(data['name'],
diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py
index 3f25ae0c9..eee655ecf 100644
--- a/tests/app/main/views/test_index.py
+++ b/tests/app/main/views/test_index.py
@@ -1,4 +1,7 @@
-from flask import url_for
+import pytest
+from flask import (url_for, current_app)
+from werkzeug.exceptions import InternalServerError
+from unittest.mock import Mock, ANY
def test_logged_in_user_redirects_to_choose_service(app_,
@@ -14,3 +17,85 @@ def test_logged_in_user_redirects_to_choose_service(app_,
response = client.get(url_for('main.sign_in', follow_redirects=True))
assert response.location == url_for('main.choose_service', _external=True)
+
+
+def test_get_feedback_page(app_):
+ with app_.test_request_context():
+ with app_.test_client() as client:
+ resp = client.get(url_for('main.feedback'))
+ assert resp.status_code == 200
+
+
+def test_post_feedback_with_no_name_email(app_, mocker):
+ mock_post = mocker.patch(
+ 'app.main.views.index.requests.post',
+ return_value=Mock(status_code=201))
+ with app_.test_request_context():
+ with app_.test_client() as client:
+ resp = client.post(url_for('main.feedback'), data={'feedback': "blah"})
+ assert resp.status_code == 302
+
+
+def test_post_feedback_with_no_name_email(app_, mocker):
+ mock_post = mocker.patch(
+ 'app.main.views.index.requests.post',
+ return_value=Mock(status_code=201))
+ with app_.test_request_context():
+ with app_.test_client() as client:
+ resp = client.post(url_for('main.feedback'), data={'feedback': "blah"})
+ assert resp.status_code == 302
+ mock_post.assert_called_with(
+ ANY,
+ data={
+ 'agent_team_id': ANY,
+ 'subject': 'Notify feedback',
+ 'message': '\n\nblah',
+ 'person_email': ANY},
+ headers=ANY)
+
+
+def test_post_feedback_with_name_email(app_, mocker):
+ mock_post = mocker.patch(
+ 'app.main.views.index.requests.post',
+ return_value=Mock(status_code=201))
+ with app_.test_request_context():
+ with app_.test_client() as client:
+ resp = client.post(
+ url_for('main.feedback'),
+ data={'feedback': "blah", 'name': "Steve Irwin", 'email_address': 'rip@gmail.com'})
+ assert resp.status_code == 302
+ mock_post.assert_called_with(
+ ANY,
+ data={
+ 'subject': 'Notify feedback',
+ 'agent_team_id': ANY,
+ 'message': 'Steve Irwin\nrip@gmail.com\nblah',
+ 'person_email': ANY},
+ headers=ANY)
+
+
+def test_log_error_on_post(app_, mocker):
+ mock_post = mocker.patch(
+ 'app.main.views.index.requests.post',
+ return_value=Mock(
+ status_code=401,
+ json=lambda: {
+ 'error_code': 'invalid_auth',
+ 'error_message': 'Please provide a valid API key or token'}))
+ with app_.test_request_context():
+ mock_logger = mocker.patch.object(app_.logger, 'error')
+ with app_.test_client() as client:
+ with pytest.raises(InternalServerError):
+ resp = client.post(
+ url_for('main.feedback'),
+ data={'feedback': "blah", 'name': "Steve Irwin", 'email_address': 'rip@gmail.com'})
+ mock_post.assert_called_with(
+ ANY,
+ data={
+ 'subject': 'Notify feedback',
+ 'agent_team_id': ANY,
+ 'message': 'Steve Irwin\nrip@gmail.com\nblah',
+ 'person_email': ANY},
+ headers=ANY)
+ mock_logger.assert_called_with(
+ "Deskpro create ticket request failed with {} '{}'".format(mock_post().status_code, mock_post().json()))