Triage tickets based on time of day and services

TL;DR, as much as possible we should work out how to prioritise tickets
and not put that burden on the user. However, there are some cases where
we can’t.

In business hours all tickets are high priority, ie we will at least
acknowledge them within 30 mins.

If we are not in business hours then we need to know if a ticket is
serious enough to get someone out of bed. Only the user can tell us
this, but we can give them some examples to help them decide.

In addition, out-of-hours tickets are only a priority if the user has
live services. Normally we can determine this and do the
priority-setting in the background.

If they can’t log in then we can’t determine what services they have. So
in this case they will need to use the emergency email address, which
only users with live services will have.

The logic for this gets fairly complex. It might be to easier to
understand what’s going on by walking through the test cases, which are
a bit more declarative.

N.B. Deskpro’s ‘urgency’ is descending, eg 10 is the most urgent and 1
is the least.
This commit is contained in:
Chris Hill-Scott
2016-12-12 11:44:11 +00:00
parent a43112db88
commit 438868257f
7 changed files with 463 additions and 9 deletions

View File

@@ -4,6 +4,17 @@ import pytest
from flask import url_for
from werkzeug.exceptions import InternalServerError
from unittest.mock import Mock, ANY
from freezegun import freeze_time
from tests.conftest import (
mock_get_services,
mock_get_services_with_no_services,
mock_get_services_with_one_service
)
from app.main.views.feedback import has_live_services, in_business_hours
def no_redirect():
return lambda _external=True: None
def test_logged_in_user_redirects_to_choose_service(app_,
@@ -26,6 +37,7 @@ def test_get_support_index_page(client):
assert resp.status_code == 200
@freeze_time('2016-12-12 12:00:00.000000')
@pytest.mark.parametrize('support_type, expected_h1', [
('problem', 'Report a problem'),
('question', 'Ask a question or give feedback'),
@@ -60,6 +72,7 @@ def test_choose_support_type(
assert page.find('form').find('p').text.strip() == expected_contact_details
@freeze_time('2016-12-12 12:00:00.000000')
@pytest.mark.parametrize('ticket_type, expected_status_code', [
('problem', 200),
('question', 200),
@@ -70,6 +83,7 @@ def test_get_feedback_page(client, ticket_type, expected_status_code):
assert response.status_code == expected_status_code
@freeze_time("2016-12-12 12:00:00.000000")
@pytest.mark.parametrize('data, expected_message, expected_person_name, expected_email, logged_in', [
(
{'feedback': "blah", 'name': 'Fred'},
@@ -101,7 +115,7 @@ def test_get_feedback_page(client, ticket_type, expected_status_code):
),
])
@pytest.mark.parametrize('ticket_type', ['problem', 'question'])
def test_post_feedback(
def test_post_problem_or_question(
client,
api_user_active,
mock_get_user,
@@ -131,15 +145,211 @@ def test_post_feedback(
'department_id': ANY,
'agent_team_id': ANY,
'subject': 'Notify feedback',
'message': expected_message.format(ticket_type),
'message': expected_message,
'person_email': expected_email,
'person_name': expected_person_name,
'label': ticket_type,
'urgency': ANY,
},
headers=ANY
)
@pytest.mark.parametrize('ticket_type, severe, is_in_business_hours, expected_urgency', [
# business hours, always urgent
('problem', True, True, 10),
('question', True, True, 10),
('problem', False, True, 10),
('question', False, True, 10),
# out of hours, non emergency, never urgent
('problem', False, False, 1),
('question', False, False, 1),
# out of hours, emergency problems are urgent
('problem', True, False, 10),
('question', True, False, 1),
])
def test_urgency(
logged_in_client,
api_user_active,
mock_get_user,
mock_get_services,
mocker,
ticket_type,
severe,
is_in_business_hours,
expected_urgency,
):
mocker.patch('app.main.views.feedback.in_business_hours', return_value=is_in_business_hours)
mock_post = mocker.patch('app.main.views.feedback.requests.post', return_value=Mock(status_code=201))
response = logged_in_client.post(
url_for('main.feedback', ticket_type=ticket_type, severe=severe),
data={'feedback': "blah"},
)
assert response.status_code == 302
assert mock_post.call_args[1]['data']['urgency'] == expected_urgency
ids, params = zip(*[
('non-logged in users always have to triage', (
'problem', False, False, True,
302, partial(url_for, 'main.triage')
)),
('trial services are never high priority', (
'problem', False, True, False,
200, no_redirect()
)),
('we can triage in hours', (
'problem', True, True, True,
200, no_redirect()
)),
('only problems are high priority', (
'question', False, True, True,
200, no_redirect()
)),
('should triage out of hours', (
'problem', False, True, True,
302, partial(url_for, 'main.triage')
))
])
@pytest.mark.parametrize(
(
'ticket_type, is_in_business_hours, logged_in, has_live_services,'
'expected_status, expected_redirect'
),
params, ids=ids
)
def test_redirects_to_triage(
client,
api_user_active,
mocker,
mock_get_user,
ticket_type,
is_in_business_hours,
logged_in,
has_live_services,
expected_status,
expected_redirect,
):
mocker.patch('app.main.views.feedback.has_live_services', return_value=has_live_services)
mocker.patch('app.main.views.feedback.in_business_hours', return_value=is_in_business_hours)
if logged_in:
client.login(api_user_active)
response = client.get(url_for('main.feedback', ticket_type=ticket_type))
assert response.status_code == expected_status
assert response.location == expected_redirect(_external=True)
@pytest.mark.parametrize('get_services_mock, expected_return_value', [
(mock_get_services, True),
(mock_get_services_with_no_services, False),
(mock_get_services_with_one_service, False),
])
def test_has_live_services(
mocker,
fake_uuid,
get_services_mock,
expected_return_value
):
get_services_mock(mocker, fake_uuid)
assert has_live_services(12345) == expected_return_value
@pytest.mark.parametrize('when, is_in_business_hours', [
('2016-06-06 09:29:59', False), # opening time, summer and winter
('2016-12-12 09:29:59', False),
('2016-06-06 09:30:00', True),
('2016-12-12 09:30:00', True),
('2016-12-12 12:00:00', True), # middle of the day
('2016-12-12 17:29:59', True), # closing time
('2016-12-12 17:30:00', False),
('2016-12-10 12:00:00', False), # Saturday
('2016-12-11 12:00:00', False), # Sunday
('2016-01-01 12:00:00', False), # Bank holiday
])
def test_in_business_hours(when, is_in_business_hours):
with freeze_time(when):
assert in_business_hours() == is_in_business_hours
@pytest.mark.parametrize('choice, expected_redirect_param', [
('yes', True),
('no', False),
])
def test_triage_redirects_to_correct_url(client, mocker, choice, expected_redirect_param):
response = client.post(url_for('main.triage'), data={'severe': choice})
assert response.status_code == 302
assert response.location == url_for(
'main.feedback',
ticket_type='problem',
severe=expected_redirect_param,
_external=True,
)
@pytest.mark.parametrize('is_in_business_hours, severe, expected_status_code, expected_redirect', [
(True, True, 200, no_redirect()),
(True, False, 200, no_redirect()),
(False, False, 200, no_redirect()),
(False, True, 302, partial(url_for, 'main.bat_phone')),
])
def test_should_be_shown_the_bat_email(
client,
active_user_with_permissions,
mocker,
service_one,
mock_get_services,
is_in_business_hours,
severe,
expected_status_code,
expected_redirect,
):
mocker.patch('app.main.views.feedback.in_business_hours', return_value=is_in_business_hours)
feedback_page = url_for('main.feedback', ticket_type='problem', severe=severe)
response = client.get(feedback_page)
assert response.status_code == expected_status_code
assert response.location == expected_redirect(_external=True)
# logged in users should never be redirected to the bat email page
client.login(active_user_with_permissions, mocker, service_one)
logged_in_response = client.get(feedback_page)
assert logged_in_response.status_code == 200
def test_bat_email_page(
client,
active_user_with_permissions,
mocker,
service_one,
):
bat_phone_page = url_for('main.bat_phone')
response = client.get(bat_phone_page)
assert response.status_code == 200
client.login(active_user_with_permissions, mocker, service_one)
logged_in_response = client.get(bat_phone_page)
assert logged_in_response.status_code == 302
assert logged_in_response.location == url_for('main.feedback', ticket_type='problem', _external=True)
@freeze_time('2016-12-12 12:00:00.000000')
@pytest.mark.parametrize('ticket_type', ['problem', 'question'])
def test_log_error_on_post(app_, mocker, ticket_type):
mock_post = mocker.patch(