remove unused support forms/pages & unused go-live form

This commit is contained in:
stvnrlly
2023-12-20 10:44:38 -05:00
parent 7a7cacbf20
commit 378717571b
34 changed files with 17 additions and 3074 deletions
+1 -6
View File
@@ -110,11 +110,7 @@ from app.notify_client.template_folder_api_client import template_folder_api_cli
from app.notify_client.template_statistics_api_client import template_statistics_client
from app.notify_client.upload_api_client import upload_api_client
from app.notify_client.user_api_client import user_api_client
from app.url_converters import (
SimpleDateTypeConverter,
TemplateTypeConverter,
TicketTypeConverter,
)
from app.url_converters import SimpleDateTypeConverter, TemplateTypeConverter
login_manager = LoginManager()
csrf = CSRFProtect()
@@ -326,7 +322,6 @@ def init_app(application):
application.url_map.converters["uuid"].to_python = lambda self, value: value
application.url_map.converters["template_type"] = TemplateTypeConverter
application.url_map.converters["ticket_type"] = TicketTypeConverter
application.url_map.converters["simple_date"] = SimpleDateTypeConverter
-51
View File
@@ -61,7 +61,6 @@ from app.main.validators import (
ValidEmail,
ValidGovEmail,
)
from app.models.feedback import PROBLEM_TICKET_TYPE, QUESTION_TICKET_TYPE
from app.models.organization import Organization
from app.utils import merge_jsonlike
from app.utils.csv import get_user_preferred_timezone
@@ -1319,49 +1318,6 @@ class CreateKeyForm(StripWhitespaceForm):
raise ValidationError("A key with this name already exists")
class SupportType(StripWhitespaceForm):
support_type = GovukRadiosField(
"How can we help you?",
choices=[
(PROBLEM_TICKET_TYPE, "Report a problem"),
(QUESTION_TICKET_TYPE, "Ask a question or give feedback"),
],
)
class SupportRedirect(StripWhitespaceForm):
who = GovukRadiosField(
"What do you need help with?",
choices=[
(
"public-sector",
"I work in the public sector and need to send emails or text messages",
),
("public", "Im a member of the public with a question for the government"),
],
param_extensions={"fieldset": {"legend": {"classes": "usa-sr-only"}}},
)
class FeedbackOrProblem(StripWhitespaceForm):
name = GovukTextInputField("Name (optional)")
email_address = email_address(label="Email address", gov_user=False, required=True)
feedback = TextAreaField(
"Your message", validators=[DataRequired(message="Cannot be empty")]
)
class Triage(StripWhitespaceForm):
severe = GovukRadiosField(
"Is it an emergency?",
choices=[
("yes", "Yes"),
("no", "No"),
],
thing="yes or no",
)
class EstimateUsageForm(StripWhitespaceForm):
volume_email = ForgivingIntegerField(
"How many emails do you expect to send in the next year?",
@@ -1905,13 +1861,6 @@ class AdminClearCacheForm(StripWhitespaceForm):
raise ValidationError("Select at least one option")
class AdminOrganizationGoLiveNotesForm(StripWhitespaceForm):
request_to_go_live_notes = TextAreaField(
"Go live notes",
filters=[lambda x: x or None],
)
class ChangeSecurityKeyNameForm(StripWhitespaceForm):
security_key_name = GovukTextInputField(
"Name of key",
+5 -234
View File
@@ -1,239 +1,10 @@
from datetime import datetime
from flask import render_template
import pytz
from flask import redirect, render_template, request, session, url_for
from flask_login import current_user
from govuk_bank_holidays.bank_holidays import BankHolidays
from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket
from app import convert_to_boolean, current_service
from app.extensions import zendesk_client
from app.main import main
from app.main.forms import FeedbackOrProblem, SupportRedirect, SupportType, Triage
from app.models.feedback import (
GENERAL_TICKET_TYPE,
PROBLEM_TICKET_TYPE,
QUESTION_TICKET_TYPE,
)
from app.utils import hide_from_search_engines
bank_holidays = BankHolidays(use_cached_holidays=True)
from app.utils.user import user_is_logged_in
@main.route("/support", methods=["GET", "POST"])
@hide_from_search_engines
@main.route("/support", methods=["GET"])
@user_is_logged_in
def support():
if current_user.is_authenticated:
form = SupportType()
if form.validate_on_submit():
return redirect(
url_for(
".feedback",
ticket_type=form.support_type.data,
)
)
else:
form = SupportRedirect()
if form.validate_on_submit():
if form.who.data == "public":
return redirect(url_for(".support_public"))
else:
return redirect(
url_for(
".feedback",
ticket_type=GENERAL_TICKET_TYPE,
)
)
return render_template("views/support/index.html", form=form)
@main.route("/support/public")
@hide_from_search_engines
def support_public():
return render_template("views/support/public.html")
@main.route("/support/triage", methods=["GET", "POST"])
@main.route("/support/triage/<ticket_type:ticket_type>", methods=["GET", "POST"])
@hide_from_search_engines
def triage(ticket_type=PROBLEM_TICKET_TYPE):
form = Triage()
if form.validate_on_submit():
return redirect(
url_for(".feedback", ticket_type=ticket_type, severe=form.severe.data)
)
return render_template(
"views/support/triage.html",
form=form,
page_title={
PROBLEM_TICKET_TYPE: "Report a problem",
GENERAL_TICKET_TYPE: "Contact Notify.gov support",
}.get(ticket_type),
)
@main.route("/support/<ticket_type:ticket_type>", methods=["GET", "POST"])
@hide_from_search_engines
def feedback(ticket_type):
form = FeedbackOrProblem()
if not form.feedback.data:
form.feedback.data = session.pop("feedback_message", "")
if request.args.get("severe") in ["yes", "no"]:
severe = convert_to_boolean(request.args.get("severe"))
else:
severe = None
out_of_hours_emergency = all(
(
ticket_type != QUESTION_TICKET_TYPE,
not in_business_hours(),
severe,
)
)
if needs_triage(ticket_type, severe):
session["feedback_message"] = form.feedback.data
return redirect(url_for(".triage", ticket_type=ticket_type))
if needs_escalation(ticket_type, severe):
return redirect(url_for(".bat_phone"))
if current_user.is_authenticated:
form.email_address.data = current_user.email_address
form.name.data = current_user.name
if form.validate_on_submit():
user_email = form.email_address.data
user_name = form.name.data or None
feedback_msg = render_template(
"support-tickets/support-ticket.txt",
content=form.feedback.data,
)
ticket = NotifySupportTicket(
subject="Notify feedback",
message=feedback_msg,
ticket_type=get_zendesk_ticket_type(ticket_type),
p1=out_of_hours_emergency,
user_name=user_name,
user_email=user_email,
org_id=current_service.organization_id if current_service else None,
org_type=current_service.organization_type if current_service else None,
service_id=current_service.id if current_service else None,
)
zendesk_client.send_ticket_to_zendesk(ticket)
return redirect(
url_for(
".thanks",
out_of_hours_emergency=out_of_hours_emergency,
email_address_provided=(
current_user.is_authenticated or bool(form.email_address.data)
),
)
)
return render_template(
"views/support/form.html",
form=form,
back_link=(
url_for(".support")
if severe is None
else url_for(".triage", ticket_type=ticket_type)
),
show_status_page_banner=(ticket_type == PROBLEM_TICKET_TYPE),
page_title={
GENERAL_TICKET_TYPE: "Contact Notify.gov support",
PROBLEM_TICKET_TYPE: "Report a problem",
QUESTION_TICKET_TYPE: "Ask a question or give feedback",
}.get(ticket_type),
)
@main.route("/support/escalate", methods=["GET", "POST"])
@hide_from_search_engines
def bat_phone():
if current_user.is_authenticated:
return redirect(url_for("main.feedback", ticket_type=PROBLEM_TICKET_TYPE))
return render_template("views/support/bat-phone.html")
@main.route("/support/thanks", methods=["GET", "POST"])
@hide_from_search_engines
def thanks():
return render_template(
"views/support/thanks.html",
out_of_hours_emergency=convert_to_boolean(
request.args.get("out_of_hours_emergency")
),
email_address_provided=convert_to_boolean(
request.args.get("email_address_provided")
),
out_of_hours=not in_business_hours(),
)
def in_business_hours():
now = datetime.utcnow().replace(tzinfo=pytz.utc)
if is_weekend(now) or is_bank_holiday(now):
return False
return london_time_today_as_utc(9, 30) <= now < london_time_today_as_utc(17, 30)
def london_time_today_as_utc(hour, minute):
return (
pytz.timezone("Europe/London")
.localize(datetime.now().replace(hour=hour, minute=minute))
.astimezone(pytz.utc)
)
def is_weekend(time):
return time.strftime("%A") in {
"Saturday",
"Sunday",
}
def is_bank_holiday(time):
return bank_holidays.is_holiday(time.date())
def needs_triage(ticket_type, severe):
return all(
(
ticket_type != QUESTION_TICKET_TYPE,
severe is None,
(not current_user.is_authenticated or current_user.live_services),
not in_business_hours(),
)
)
def needs_escalation(ticket_type, severe):
return all(
(
ticket_type != QUESTION_TICKET_TYPE,
severe,
not current_user.is_authenticated,
not in_business_hours(),
)
)
def get_zendesk_ticket_type(ticket_type):
# Zendesk has 4 ticket types - "problem", "incident", "task" and "question".
# We don't want to use a Zendesk "problem" ticket type when someone reports a
# Notify problem because they are designed to group multiple incident tickets together,
# allowing them to be solved as a group.
if ticket_type == PROBLEM_TICKET_TYPE:
return NotifySupportTicket.TYPE_INCIDENT
return NotifySupportTicket.TYPE_QUESTION
return render_template("views/support/index.html")
-23
View File
@@ -13,7 +13,6 @@ from app.main.forms import (
AdminNewOrganizationForm,
AdminNotesForm,
AdminOrganizationDomainsForm,
AdminOrganizationGoLiveNotesForm,
InviteOrgUserForm,
OrganizationOrganizationTypeForm,
RenameOrganizationForm,
@@ -313,28 +312,6 @@ def edit_organization_domains(org_id):
)
@main.route(
"/organizations/<uuid:org_id>/settings/edit-go-live-notes", methods=["GET", "POST"]
)
@user_is_platform_admin
def edit_organization_go_live_notes(org_id):
form = AdminOrganizationGoLiveNotesForm()
if form.validate_on_submit():
organizations_client.update_organization(
org_id, request_to_go_live_notes=form.request_to_go_live_notes.data
)
return redirect(url_for(".organization_settings", org_id=org_id))
org = organizations_client.get_organization(org_id)
form.request_to_go_live_notes.data = org["request_to_go_live_notes"]
return render_template(
"views/organizations/organization/settings/edit-go-live-notes.html",
form=form,
)
@main.route("/organizations/<uuid:org_id>/settings/notes", methods=["GET", "POST"])
@user_is_platform_admin
def edit_organization_notes(org_id):
+1 -83
View File
@@ -13,7 +13,6 @@ from flask import (
)
from flask_login import current_user
from notifications_python_client.errors import HTTPError
from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket
from app import (
billing_api_client,
@@ -28,7 +27,6 @@ from app.event_handlers import (
create_resume_service_event,
create_suspend_service_event,
)
from app.extensions import zendesk_client
from app.formatters import email_safe
from app.main import main
from app.main.forms import (
@@ -41,7 +39,6 @@ from app.main.forms import (
AdminServiceRateLimitForm,
AdminServiceSMSAllowanceForm,
AdminSetOrganizationForm,
EstimateUsageForm,
RenameServiceForm,
SearchByNameForm,
ServiceContactDetailsForm,
@@ -54,11 +51,7 @@ from app.main.forms import (
)
from app.utils import DELIVERED_STATUSES, FAILURE_STATUSES, SENDING_STATUSES
from app.utils.time import parse_naive_dt
from app.utils.user import (
user_has_permissions,
user_is_gov_user,
user_is_platform_admin,
)
from app.utils.user import user_has_permissions, user_is_platform_admin
PLATFORM_ADMIN_SERVICE_PERMISSIONS = OrderedDict(
[
@@ -120,81 +113,6 @@ def service_name_change(service_id):
)
@main.route(
"/services/<uuid:service_id>/service-settings/request-to-go-live/estimate-usage",
methods=["GET", "POST"],
)
@user_has_permissions("manage_service")
def estimate_usage(service_id):
form = EstimateUsageForm(
volume_email=current_service.volume_email,
volume_sms=current_service.volume_sms,
consent_to_research={
True: "yes",
False: "no",
}.get(current_service.consent_to_research),
)
if form.validate_on_submit():
current_service.update(
volume_email=form.volume_email.data,
volume_sms=form.volume_sms.data,
consent_to_research=(form.consent_to_research.data == "yes"),
)
return redirect(
url_for(
"main.request_to_go_live",
service_id=service_id,
)
)
return render_template(
"views/service-settings/estimate-usage.html",
form=form,
)
@main.route(
"/services/<uuid:service_id>/service-settings/request-to-go-live", methods=["GET"]
)
@user_has_permissions("manage_service")
def request_to_go_live(service_id):
if current_service.live:
return render_template("views/service-settings/service-already-live.html")
return render_template("views/service-settings/request-to-go-live.html")
@main.route(
"/services/<uuid:service_id>/service-settings/request-to-go-live", methods=["POST"]
)
@user_has_permissions("manage_service")
@user_is_gov_user
def submit_request_to_go_live(service_id):
ticket_message = render_template("support-tickets/go-live-request.txt") + "\n"
ticket = NotifySupportTicket(
subject=f"Request to go live - {current_service.name}",
message=ticket_message,
ticket_type=NotifySupportTicket.TYPE_QUESTION,
user_name=current_user.name,
user_email=current_user.email_address,
requester_sees_message_content=False,
org_id=current_service.organization_id,
org_type=current_service.organization_type,
service_id=current_service.id,
)
zendesk_client.send_ticket_to_zendesk(ticket)
current_service.update(go_live_user=current_user.id)
flash(
"Thanks for your request to go live. Well get back to you within one working day.",
"default",
)
return redirect(url_for(".service_settings", service_id=service_id))
@main.route(
"/services/<uuid:service_id>/service-settings/switch-live", methods=["GET", "POST"]
)
-3
View File
@@ -113,9 +113,6 @@ class ServiceEvent(Event):
def format_service_callback_api(self):
return "Updated the callback for delivery receipts"
def format_go_live_user(self):
return "Requested for this service to go live"
class APIKeyEvent(Event):
relevant = True
-3
View File
@@ -1,3 +0,0 @@
QUESTION_TICKET_TYPE = "ask-question-give-feedback"
PROBLEM_TICKET_TYPE = "report-problem"
GENERAL_TICKET_TYPE = "general"
-2
View File
@@ -25,7 +25,6 @@ class Organization(JSONModel, SortByNameMixin):
"active",
"organization_type",
"domains",
"request_to_go_live_notes",
"count_of_live_services",
"billing_contact_email_addresses",
"billing_contact_names",
@@ -71,7 +70,6 @@ class Organization(JSONModel, SortByNameMixin):
self.name = None
self.domains = []
self.organization_type = None
self.request_to_go_live_notes = None
@property
def organization_type_label(self):
-18
View File
@@ -27,8 +27,6 @@ class Service(JSONModel, SortByNameMixin):
"contact_link",
"count_as_live",
"email_from",
"go_live_at",
"go_live_user",
"id",
"inbound_api",
"message_limit",
@@ -370,22 +368,6 @@ class Service(JSONModel, SortByNameMixin):
)
)
@property
def go_live_checklist_completed(self):
return all(
(
bool(self.volumes),
self.has_team_members,
self.has_templates,
not self.needs_to_add_email_reply_to_address,
not self.needs_to_change_sms_sender,
)
)
@property
def go_live_checklist_completed_as_yes_no(self):
return "Yes" if self.go_live_checklist_completed else "No"
@cached_property
def free_sms_fragment_limit(self):
return billing_api_client.get_free_sms_fragment_limit_for_year(self.id) or 0
-12
View File
@@ -38,12 +38,7 @@ class Navigation:
class HeaderNavigation(Navigation):
mapping = {
"support": {
"bat_phone",
"feedback",
"support",
"support_public",
"thanks",
"triage",
},
"features": {
"features",
@@ -101,9 +96,7 @@ class HeaderNavigation(Navigation):
"manage_users",
"remove_user_from_service",
"usage",
"estimate_usage",
"link_service_to_organization",
"request_to_go_live",
"service_add_email_reply_to",
"service_add_sms_sender",
"service_confirm_delete_email_reply_to",
@@ -127,7 +120,6 @@ class HeaderNavigation(Navigation):
"set_free_sms_allowance",
"set_message_limit",
"set_rate_limit",
"submit_request_to_go_live",
},
"pricing": {
"how_to_pay",
@@ -245,9 +237,7 @@ class MainNavigation(Navigation):
"usage",
},
"settings": {
"estimate_usage",
"link_service_to_organization",
"request_to_go_live",
"service_add_email_reply_to",
"service_add_sms_sender",
"service_confirm_delete_email_reply_to",
@@ -271,7 +261,6 @@ class MainNavigation(Navigation):
"set_free_sms_allowance",
"set_message_limit",
"set_rate_limit",
"submit_request_to_go_live",
},
"api-integration": {
"api_callbacks",
@@ -316,7 +305,6 @@ class OrgNavigation(Navigation):
"settings": {
"edit_organization_billing_details",
"edit_organization_domains",
"edit_organization_go_live_notes",
"edit_organization_name",
"edit_organization_notes",
"edit_organization_type",
@@ -1,31 +0,0 @@
{% set service = current_service -%}
{% set organization = service.organization -%}
{% set user = current_user -%}
Service: {{ service.name }}
{{ url_for('main.service_dashboard', service_id=service.id, _external=True) }}
---
Organization type: {{ service.organization_type_label }}
{%- if organization.name %} (organization is {{ organization.name }})
{%- else %} (domain is {{ user.email_domain }})
{%- endif %}.
{%- if organization.request_to_go_live_notes %} {{ organization.request_to_go_live_notes }}{% endif %}
{%- if organization.agreement_signed_by %}
Agreement signed by: {{ organization.agreement_signed_by.email_address }}
{% endif -%}
{%- if organization.agreement_signed_on_behalf_of_email_address -%}
Agreement signed on behalf of: {{ organization.agreement_signed_on_behalf_of_email_address }}
{%- endif %}
Emails in next year: {{ service.volume_email|format_thousands }}
Text messages in next year: {{ service.volume_sms|format_thousands }}
Consent to research: {{ service.consent_to_research|format_yes_no }}
Other live services for that user: {{ user.live_services|format_yes_no }}
Service reply-to address: {{ service.default_email_reply_to_address or "not set" }}
---
Request sent by {{ user.email_address }}
Requesters user page: {{ url_for('main.user_information', user_id=user.id, _external=True) }}
@@ -1,5 +0,0 @@
{{ content }}
{% if current_service -%}
Service: "{{ current_service.name }}"
{{ url_for('main.service_dashboard', service_id=current_service.id, _external=True) }}
{% endif %}
+1 -1
View File
@@ -53,7 +53,7 @@
{% if not current_user.is_authenticated or not current_service %}
<p>When youre ready to send messages to people outside your team, go to the <b class="bold">Settings</b> page and select <b class="bold">Request to go live</b>. Well approve your request within one working day.</p>
{% else %}
<p>You should <a class="usa-link" href="{{ url_for('.request_to_go_live', service_id=current_service.id) }}">request to go live</a> when youre ready to send messages to people outside your team. Well approve your request within one working day.</p>
<p>You should <a class="usa-link" href="{{ url_for('.support') }}">request to go live</a> when youre ready to send messages to people outside your team. Well approve your request within one working day.</p>
{% endif %}
<!-- <p>Check <a class="usa-link" href="{{ url_for('main.how_to_pay') }}">how to pay</a> if youre planning to exceed the <a class="usa-link" href="{{ url_for('.pricing', _anchor='text-messages') }}">free text message allowance</a>.</p> -->
</li>
@@ -1,30 +0,0 @@
{% extends "org_template.html" %}
{% from "components/form.html" import form_wrapper %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/page-header.html" import page_header %}
{% from "components/textbox.html" import textbox %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block org_page_title %}
Edit request to go live notes
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": url_for('.organization_settings', org_id=current_org.id) }) }}
{% endblock %}
{% block maincolumn_content %}
{{ page_header("Edit request to go live notes") }}
<div class="grid-row">
<div class="grid-col-10">
<p>
Text entered here will be displayed in the Zendesk ticket when a service
belonging to this organization requests to go live.
</p>
{% call form_wrapper() %}
{{ textbox(form.request_to_go_live_notes, width='1-1', rows=3, autosize=True) }}
{{ page_footer('Save') }}
{% endcall %}
</div>
</div>
{% endblock %}
@@ -34,16 +34,6 @@
)
}}
{% endcall %}
{% call row() %}
{{ text_field('Request to go live notes') }}
{{ optional_text_field(current_org.request_to_go_live_notes, default='None') }}
{{ edit_field(
'Change',
url_for('.edit_organization_go_live_notes', org_id=current_org.id),
suffix='go live notes for the organization'
)
}}
{% endcall %}
{% call row() %}
{{ text_field('Billing details')}}
+1 -1
View File
@@ -206,7 +206,7 @@
</p>
<p>
Problems or comments?
<a class="usa-link" href="{{ url_for('main.support') }}">Give feedback</a>.
<a class="usa-link" href="{{ url_for('main.support') }}">Contact us</a>.
</p>
{% endif %}
@@ -1,42 +0,0 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/form.html" import form_wrapper %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block service_page_title %}
Tell us how many messages you expect to send
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": url_for('main.request_to_go_live', service_id=current_service.id) }) }}
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="grid-col-12">
{% if not form.at_least_one_volume_filled %}
{% call banner_wrapper(type='dangerous') %}
<h1 class='banner-title'>
Enter the number of messages you expect to send in the next year
</h1>
{% endcall %}
{% else %}
{{ page_header('Tell us how many messages you expect to send') }}
{% endif %}
{% call form_wrapper() %}
<div class="form-group">
{{ form.volume_email(param_extensions={
"hint": {"text": "For example, 50,000"},
}) }}
{{ form.volume_sms(param_extensions={
"hint": {"text": "For example, 50,000"},
}) }}
</div>
{{ form.consent_to_research }}
{{ page_footer('Continue') }}
{% endcall %}
</div>
</div>
{% endblock %}
@@ -1,75 +0,0 @@
{% extends "withnav_template.html" %}
{% from "components/form.html" import form_wrapper %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/task-list.html" import task_list_wrapper, task_list_item %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block service_page_title %}
Before you request to go live
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": url_for('main.service_settings', service_id=current_service.id) }) }}
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="grid-col-12">
{{ page_header('Before you request to go live') }}
{% call task_list_wrapper() %}
{{ task_list_item(
current_service.has_estimated_usage,
'Tell us how many messages you expect to send',
url_for('main.estimate_usage', service_id=current_service.id),
) }}
{{ task_list_item(
current_service.has_team_members,
'Add a team member who can manage settings, team and usage',
url_for('main.manage_users', service_id=current_service.id),
) }}
{{ task_list_item(
current_service.has_templates,
'Add templates with examples of the content you plan to send',
url_for('main.choose_template', service_id=current_service.id),
) }}
{% if current_service.intending_to_send_email %}
{{ task_list_item(
current_service.has_email_reply_to_address,
'Add a reply-to email address',
url_for('main.service_email_reply_to', service_id=current_service.id),
) }}
{% endif %}
{% if (
current_service.intending_to_send_sms
and current_service.shouldnt_use_govuk_as_sms_sender
) %}
{{ task_list_item(
not current_service.sms_sender_is_govuk,
'Change your text message sender name',
url_for('main.service_sms_senders', service_id=current_service.id),
) }}
{% endif %}
{% endcall %}
{% if not current_user.is_gov_user %}
<p>
Only team members with a government email address can request to go live.
</p>
{% elif (not current_service.go_live_checklist_completed) %}
<p>
You must complete these steps before you can request to go live.
</p>
{% else %}
<p>
When we receive your request well get back to you within one working day.
</p>
<p class="bottom-gutter">
By requesting to go live youre agreeing to our <a class="usa-link" href="{{ url_for('.terms') }}">terms of use</a>.
</p>
{% call form_wrapper() %}
{{ page_footer('Request to go live') }}
{% endcall %}
{% endif %}
</div>
</div>
{% endblock %}
@@ -1,28 +0,0 @@
{% extends "withnav_template.html" %}
{% from "components/page-header.html" import page_header %}
{% block service_page_title %}
Your service is already live
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="grid-col-12">
{{ page_header('Your service is already live') }}
<p>
{% if current_service.go_live_at %}
{{ current_service.name }} went live on {{ current_service.go_live_at | format_date_normal }}.
{% else %}
{{ current_service.name }} is already live.
{% endif %}
</p>
<p>
<a class="usa-link" href="{{ url_for('.choose_account') }}">Switch service</a>
if you want to make a different service live.
</p>
</div>
</div>
{% endblock %}
@@ -1,46 +0,0 @@
{% extends "withoutnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/page-header.html" import page_header %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block per_page_title %}
Out of hours emergencies
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": url_for('.support') }) }}
{% endblock %}
{% block maincolumn_content %}
{{ page_header('Out of hours emergencies')}}
<div class="grid-row">
<div class="grid-col-8">
<p>
First, check the
<a class="usa-link" href="https://status.notifications.service.gov.uk">system status page</a>.
You do not need to contact us if
the problem youre having is listed on that page.
</p>
<p>
Otherwise, contact us using the emergency email address we
gave you or your service manager when we made your service live.
</p>
<p>
Well reply within 30 minutes and give you hourly updates
until the problems fixed.
</p>
<p>
We do not offer out of hours support if your service is in
trial mode.
</p>
<h2 class="font-body-lg">Any other problems</h2>
<p class="bottom-gutter-2">
<a class="usa-link" href="{{ url_for('main.feedback', ticket_type='report-problem', severe='no') }}">Fill in this form</a>
and well get back to you by the next working day.
</p>
</div>
</div>
{% endblock %}
-42
View File
@@ -1,42 +0,0 @@
{% extends "withoutnav_template.html" %}
{% from "components/textbox.html" import textbox %}
{% from "components/page-footer.html" import sticky_page_footer %}
{% from "components/page-header.html" import page_header %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block per_page_title %}
{{ page_title }}
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": back_link }) }}
{% endblock %}
{% block maincolumn_content %}
{{ page_header(page_title) }}
<div class="grid-row">
<div class="grid-col-8">
{% if show_status_page_banner %}
<div class="panel panel-border-wide">
<p>
Check our <a class="usa-link" href="https://status.notifications.service.gov.uk">system status</a>
page to see if there are any known issues with Notify.gov.
</p>
</div>
{% endif %}
{% call form_wrapper() %}
{{ textbox(form.feedback, width='1-1', hint='', rows=10, autosize=True) }}
{% if not current_user.is_authenticated %}
{{ form.name(param_extensions={"classes": ""}) }}
{{ form.email_address(param_extensions={"classes": ""}) }}
{% else %}
<p>Well reply to {{ current_user.email_address }}</p>
{% endif %}
{{ sticky_page_footer('Send') }}
{% endcall %}
</div>
</div>
{% endblock %}
-50
View File
@@ -1,50 +0,0 @@
{% extends "withoutnav_template.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block per_page_title %}
The Notify.gov service is for people who work in the government
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": url_for('.support') }) }}
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="grid-col-8">
{{ page_header('The Notify.gov service is for people who work in the government') }}
<p>
We cannot give advice to the public. We do not have access to information about you held by government departments.
</p>
<p>
There are other pages on Notify.gov where you can get help:
</p>
<h2 class="govuk-heading-m">
<a class="usa-link" href="https://www.gov.uk/coronavirus">Coronavirus (COVID-19)</a>
</h2>
<p>
Find guidance and support.
</p>
<h2 class="govuk-heading-m">
<a class="usa-link" href="https://www.gov.uk/contact">Contact the government</a>
</h2>
<p>
Ask about benefits, driving, transport, tax, and more.
</p>
<h2 class="govuk-heading-m">
<a class="usa-link" href="https://www.gov.uk/report-suspicious-emails-websites-phishing">Report internet scams and phishing</a>
</h2>
<p>
Advice on suspicious emails and text messages.
</p>
</div>
</div>
{% endblock %}
-38
View File
@@ -1,38 +0,0 @@
{% extends "withoutnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/page-header.html" import page_header %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block per_page_title %}
Thanks for contacting us
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": url_for('.support') }) }}
{% endblock %}
{% block maincolumn_content %}
{{ page_header('Thanks for contacting us') }}
<p>
{% if out_of_hours_emergency %}
Well reply in the next 30 minutes.
{% else %}
{% if email_address_provided %}
{% if out_of_hours %}
Well reply within one working day.
{% else %}
Well aim to read your message in the next 30 minutes and well reply within one
working day.
{% endif %}
{% else %}
{% if out_of_hours %}
Well read your message when were back in the office.
{% else %}
Well aim to read your message in the next 30 minutes.
{% endif %}
{% endif %}
{% endif %}
</p>
{% endblock %}
-62
View File
@@ -1,62 +0,0 @@
{% extends "withoutnav_template.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/page-header.html" import page_header %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block per_page_title %}
{{ page_title }}
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": url_for('.support') }) }}
{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">
<div class="grid-col-8">
{{ page_header(page_title) }}
{% call form_wrapper() %}
{{ form.severe }}
{{ page_footer('Continue') }}
{% endcall %}
<h2 class="heading-small">
Its only an emergency if:
</h2>
<ul class="list list-bullet">
<li>
no one in your team can log in
</li>
<li>
you get a technical difficulties error message when you try
to upload a file
</li>
<li>
you get a 500 response code when you try to send messages
using the API
</li>
</ul>
<h2 class="heading-small">
Its not an emergency if:
</h2>
<ul class="list list-bullet bottom-gutter">
<li>
all your messages stay in sending for a few hours
</li>
<li>
you send the wrong message by accident
</li>
<li>
a team member uses Notify.gov to send an
inappropriate message
</li>
<li>
your system is telling the Notify.gov API to send the wrong
message
</li>
</ul>
</div>
</div>
{% endblock %}
+1 -1
View File
@@ -17,7 +17,7 @@
{% if current_service and current_service.trial_mode %}
<p>
To remove these restrictions, you can <a class="usa-link" href="{{ url_for('.request_to_go_live', service_id=current_service.id) }}">request to go live</a>.</p>
To remove these restrictions, you can <a class="usa-link" href="{{ url_for('.support') }}">request to go live</a>.</p>
{% else %}
<p>
To remove these restrictions:
-9
View File
@@ -1,10 +1,5 @@
from werkzeug.routing import BaseConverter
from app.models.feedback import (
GENERAL_TICKET_TYPE,
PROBLEM_TICKET_TYPE,
QUESTION_TICKET_TYPE,
)
from app.models.service import Service
@@ -12,9 +7,5 @@ class TemplateTypeConverter(BaseConverter):
regex = "(?:{})".format("|".join(Service.TEMPLATE_TYPES))
class TicketTypeConverter(BaseConverter):
regex = f"(?:{PROBLEM_TICKET_TYPE}|{QUESTION_TICKET_TYPE}|{GENERAL_TICKET_TYPE})"
class SimpleDateTypeConverter(BaseConverter):
regex = r"([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"
-152
View File
@@ -1,152 +0,0 @@
"""
This script can be used to retrieve Zendesk tickets.
This can be run locally if you set the ZENDESK_API_KEY. Or the script can be run from a flask shell from a ssh session.
"""
# flake8: noqa: T001 (print)
import csv
import os
import urllib.parse
import requests
# Group: 3rd Line--Notify Support
NOTIFY_GROUP_ID = 360000036529
# Organization: GDS
NOTIFY_ORG_ID = 21891972
# the account used to authenticate with. If no requester is provided, the ticket will come from this account.
NOTIFY_ZENDESK_EMAIL = "zd-api-notify@digital.cabinet-office.gov.uk"
ZENDESK_API_KEY = os.environ.get("ZENDESK_API_KEY")
def get_tickets():
ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}"
query_params = "type:ticket group:{}".format(NOTIFY_GROUP_ID)
query_params = urllib.parse.quote(query_params)
next_page = ZENDESK_TICKET_URL.format(query_params)
with open("zendesk_ticket_data.csv", "w") as csvfile:
fieldnames = [
"Service id",
"Ticket id",
"Subject line",
"Date ticket created",
"Tags",
]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
while next_page:
print(next_page)
response = requests.get(
next_page,
headers={"Content-type": "application/json"},
auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY),
)
data = response.json()
print(data)
for row in data["results"]:
service_url = [
x
for x in row["description"].split("\n")
if x.startswith(
"https://www.notifications.service.gov.uk/services/"
)
]
service_url = service_url[0][50:] if len(service_url) > 0 else None
if service_url:
writer.writerow(
{
"Service id": service_url,
"Ticket id": row["id"],
"Subject line": row["subject"],
"Date ticket created": row["created_at"],
"Tags": row.get("tags", ""),
}
)
next_page = data["next_page"]
def get_tickets_without_service_id():
ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}"
query_params = "type:ticket group:{}".format(NOTIFY_GROUP_ID)
query_params = urllib.parse.quote(query_params)
next_page = ZENDESK_TICKET_URL.format(query_params)
with open("zendesk_ticket_data_without_service.csv", "w") as csvfile:
fieldnames = [
"Ticket id",
"Subject line",
"Date ticket created",
"Tags",
]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
while next_page:
print(next_page)
response = requests.get(
next_page,
headers={"Content-type": "application/json"},
auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY),
)
data = response.json()
print(data)
for row in data["results"]:
service_url = [
x
for x in row["description"].split("\n")
if x.startswith(
"https://www.notifications.service.gov.uk/services/"
)
]
service_url = service_url[0][50:] if len(service_url) > 0 else None
if not service_url:
writer.writerow(
{
"Ticket id": row["id"],
"Subject line": row["subject"],
"Date ticket created": row["created_at"],
"Tags": row.get("tags", ""),
}
)
next_page = data["next_page"]
def get_tickets_with_description():
ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}"
query_params = "type:ticket group:{}, created>2019-07-01".format(NOTIFY_GROUP_ID)
query_params = urllib.parse.quote(query_params)
next_page = ZENDESK_TICKET_URL.format(query_params)
with open("zendesk_ticket.csv", "w") as csvfile:
fieldnames = [
"Ticket id",
"Subject line",
"Description",
"Date ticket created",
"Tags",
]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
while next_page:
print(next_page)
response = requests.get(
next_page,
headers={"Content-type": "application/json"},
auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY),
)
data = response.json()
print(data)
for row in data["results"]:
writer.writerow(
{
"Ticket id": row["id"],
"Subject line": row["subject"],
"Description": row["description"],
"Date ticket created": row["created_at"],
"Tags": row.get("tags", ""),
}
)
next_page = data["next_page"]
-2
View File
@@ -222,7 +222,6 @@ def organization_json(
agreement_signed_on_behalf_of_name=None,
agreement_signed_on_behalf_of_email_address=None,
organization_type="federal",
request_to_go_live_notes=None,
notes=None,
billing_contact_email_addresses=None,
billing_contact_names=None,
@@ -248,7 +247,6 @@ def organization_json(
"agreement_signed_on_behalf_of_name": agreement_signed_on_behalf_of_name,
"agreement_signed_on_behalf_of_email_address": agreement_signed_on_behalf_of_email_address,
"domains": domains or [],
"request_to_go_live_notes": request_to_go_live_notes,
"count_of_live_services": len(services),
"notes": notes,
"billing_contact_email_addresses": billing_contact_email_addresses,
@@ -928,7 +928,6 @@ def test_organization_settings_for_platform_admin(
"Label Value Action",
"Name Test organization Change organization name",
"Sector Federal government Change sector for the organization",
"Request to go live notes None Change go live notes for the organization",
"Billing details None Change billing details for the organization",
"Notes None Change the notes for the organization",
"Known email domains None Change known email domains for the organization",
@@ -1346,48 +1345,6 @@ def test_update_organization_with_non_unique_name(
)
def test_get_edit_organization_go_live_notes_page(
client_request,
platform_admin_user,
mock_get_organization,
organization_one,
):
client_request.login(platform_admin_user)
page = client_request.get(
".edit_organization_go_live_notes",
org_id=organization_one["id"],
)
assert page.find("textarea", id="request_to_go_live_notes")
@pytest.mark.parametrize(
("input_note", "saved_note"),
[("Needs permission", "Needs permission"), (" ", None)],
)
def test_post_edit_organization_go_live_notes_updates_go_live_notes(
client_request,
platform_admin_user,
mock_get_organization,
mock_update_organization,
organization_one,
input_note,
saved_note,
):
client_request.login(platform_admin_user)
client_request.post(
".edit_organization_go_live_notes",
org_id=organization_one["id"],
_data={"request_to_go_live_notes": input_note},
_expected_redirect=url_for(
".organization_settings",
org_id=organization_one["id"],
),
)
mock_update_organization.assert_called_once_with(
organization_one["id"], request_to_go_live_notes=saved_note
)
def test_organization_settings_links_to_edit_organization_notes_page(
mocker,
mock_get_organization,
File diff suppressed because it is too large Load Diff
+5 -802
View File
@@ -1,812 +1,15 @@
from functools import partial
from unittest.mock import ANY, PropertyMock
import pytest
from flask import url_for
from freezegun import freeze_time
from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket
from app.main.views.feedback import in_business_hours
from app.models.feedback import (
GENERAL_TICKET_TYPE,
PROBLEM_TICKET_TYPE,
QUESTION_TICKET_TYPE,
)
from tests.conftest import SERVICE_ONE_ID, normalize_spaces
def no_redirect():
return lambda: None
@pytest.mark.skip(reason="Not currently using Zendesk")
def test_get_support_index_page(
client_request,
):
page = client_request.get(".support")
assert page.select_one("form")["method"] == "post"
assert "action" not in page.select_one("form")
assert normalize_spaces(page.select_one("h1").text) == "Support"
assert (
normalize_spaces(page.select_one("form label[for=support_type-0]").text)
== "Report a problem"
)
assert page.select_one("form input#support_type-0")["value"] == "report-problem"
assert (
normalize_spaces(page.select_one("form label[for=support_type-1]").text)
== "Ask a question or give feedback"
)
assert (
page.select_one("form input#support_type-1")["value"]
== "ask-question-give-feedback"
)
assert (
normalize_spaces(page.select_one("form button[type=submit]").text) == "Continue"
)
@pytest.mark.skip(reason="Not currently using Zendesk")
def test_get_support_index_page_when_signed_out(
client_request,
):
client_request.logout()
page = client_request.get(".support")
assert page.select_one("form")["method"] == "post"
assert "action" not in page.select_one("form")
assert normalize_spaces(page.select_one("form label[for=who-0]").text) == (
"I work in the public sector and need to send emails or text messages"
)
assert page.select_one("form input#who-0")["value"] == "public-sector"
assert normalize_spaces(page.select_one("form label[for=who-1]").text) == (
"Im a member of the public with a question for the government"
)
assert page.select_one("form input#who-1")["value"] == "public"
assert (
normalize_spaces(page.select_one("form button[type=submit]").text) == "Continue"
)
@freeze_time("2016-12-12 12:00:00.000000")
@pytest.mark.parametrize(
("support_type", "expected_h1"),
[
(PROBLEM_TICKET_TYPE, "Report a problem"),
(QUESTION_TICKET_TYPE, "Ask a question or give feedback"),
],
)
def test_choose_support_type(
client_request,
mock_get_non_empty_organizations_and_services_for_user,
support_type,
expected_h1,
):
page = client_request.post(
"main.support",
_data={"support_type": support_type},
_follow_redirects=True,
)
assert page.h1.string.strip() == expected_h1
assert not page.select_one("input[name=name]")
assert not page.select_one("input[name=email_address]")
assert page.find("form").find("p").text.strip() == (
"Well reply to test@user.gsa.gov"
)
from tests.conftest import normalize_spaces
@freeze_time("2016-12-12 12:00:00.000000")
def test_get_support_as_someone_in_the_public_sector(
mocker,
active_user_with_permissions,
client_request,
):
client_request.logout()
page = client_request.post(
page = client_request.get(
"main.support",
_data={"who": "public-sector"},
_follow_redirects=True,
)
assert normalize_spaces(page.select("h1")) == ("Contact Notify.gov support")
assert page.select_one("form textarea[name=feedback]")
assert page.select_one("form input[name=name]")
assert page.select_one("form input[name=email_address]")
assert page.select_one("form button[type=submit]")
def test_get_support_as_member_of_public(
client_request,
):
client_request.logout()
page = client_request.post(
"main.support",
_data={"who": "public"},
_follow_redirects=True,
)
assert normalize_spaces(page.select("h1")) == (
"The Notify.gov service is for people who work in the government"
)
assert len(page.select("h2 a")) == 3
assert not page.select("form")
assert not page.select("input")
assert not page.select("form [type=submit]")
@freeze_time("2016-12-12 12:00:00.000000")
@pytest.mark.parametrize(
("ticket_type", "expected_status_code"),
[(PROBLEM_TICKET_TYPE, 200), (QUESTION_TICKET_TYPE, 200), ("gripe", 404)],
)
def test_get_feedback_page(client_request, ticket_type, expected_status_code):
client_request.logout()
client_request.get(
"main.feedback",
ticket_type=ticket_type,
_expected_status=expected_status_code,
)
@freeze_time("2016-12-12 12:00:00.000000")
@pytest.mark.parametrize(
("ticket_type", "zendesk_ticket_type"),
[
(PROBLEM_TICKET_TYPE, "incident"),
(QUESTION_TICKET_TYPE, "question"),
(GENERAL_TICKET_TYPE, "question"),
],
)
def test_passed_non_logged_in_user_details_through_flow(
client_request, mocker, ticket_type, zendesk_ticket_type
):
client_request.logout()
mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
mock_send_ticket_to_zendesk = mocker.patch(
"app.main.views.feedback.zendesk_client.send_ticket_to_zendesk",
autospec=True,
)
data = {
"feedback": "blah",
"name": "Anne Example",
"email_address": "anne@example.com",
}
client_request.post(
"main.feedback",
ticket_type=ticket_type,
_data=data,
_expected_redirect=url_for(
"main.thanks",
out_of_hours_emergency=False,
email_address_provided=True,
),
)
mock_create_ticket.assert_called_once_with(
ANY,
subject="Notify feedback",
message="blah\n",
ticket_type=zendesk_ticket_type,
p1=False,
user_name="Anne Example",
user_email="anne@example.com",
org_id=None,
org_type=None,
service_id=None,
)
mock_send_ticket_to_zendesk.assert_called_once()
@freeze_time("2016-12-12 12:00:00.000000")
@pytest.mark.parametrize(
"data",
[
{"feedback": "blah"},
{"feedback": "blah", "name": "Ignored", "email_address": "ignored@email.com"},
],
)
@pytest.mark.parametrize(
("ticket_type", "zendesk_ticket_type"),
[
(PROBLEM_TICKET_TYPE, "incident"),
(QUESTION_TICKET_TYPE, "question"),
(GENERAL_TICKET_TYPE, "question"),
],
)
def test_passes_user_details_through_flow(
client_request,
mock_get_non_empty_organizations_and_services_for_user,
mocker,
ticket_type,
zendesk_ticket_type,
data,
):
mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
mock_send_ticket_to_zendesk = mocker.patch(
"app.main.views.feedback.zendesk_client.send_ticket_to_zendesk",
autospec=True,
)
client_request.post(
"main.feedback",
ticket_type=ticket_type,
_data=data,
_expected_status=302,
_expected_redirect=url_for(
"main.thanks",
email_address_provided=True,
out_of_hours_emergency=False,
),
)
mock_create_ticket.assert_called_once_with(
ANY,
subject="Notify feedback",
message=ANY,
ticket_type=zendesk_ticket_type,
p1=False,
user_name="Test User",
user_email="test@user.gsa.gov",
org_id=None,
org_type="federal",
service_id=SERVICE_ONE_ID,
)
assert mock_create_ticket.call_args[1]["message"] == "\n".join(
[
"blah",
'Service: "service one"',
url_for(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
_external=True,
),
"",
]
)
mock_send_ticket_to_zendesk.assert_called_once()
@freeze_time("2016-12-12 12:00:00.000000")
@pytest.mark.parametrize(
"data",
[
{"feedback": "blah", "name": "Fred"},
{"feedback": "blah"},
],
)
@pytest.mark.parametrize(
"ticket_type",
[
PROBLEM_TICKET_TYPE,
QUESTION_TICKET_TYPE,
],
)
def test_email_address_required_for_problems_and_questions(
client_request,
mocker,
data,
ticket_type,
):
mocker.patch("app.main.views.feedback.zendesk_client")
client_request.logout()
page = client_request.post(
"main.feedback", ticket_type=ticket_type, _data=data, _expected_status=200
)
assert normalize_spaces(page.select_one(".usa-error-message").text) == (
"Error: Cannot be empty"
)
@freeze_time("2016-12-12 12:00:00.000000")
@pytest.mark.parametrize("ticket_type", [PROBLEM_TICKET_TYPE, QUESTION_TICKET_TYPE])
def test_email_address_must_be_valid_if_provided_to_support_form(
client_request,
mocker,
ticket_type,
):
client_request.logout()
page = client_request.post(
"main.feedback",
ticket_type=ticket_type,
_data={
"feedback": "blah",
"email_address": "not valid",
},
_expected_status=200,
)
assert normalize_spaces(page.select_one("span.usa-error-message").text) == (
"Error: Enter a valid email address"
)
@pytest.mark.parametrize(
("ticket_type", "severe", "is_in_business_hours", "is_out_of_hours_emergency"),
[
# business hours, never an emergency
(PROBLEM_TICKET_TYPE, "yes", True, False),
(QUESTION_TICKET_TYPE, "yes", True, False),
(PROBLEM_TICKET_TYPE, "no", True, False),
(QUESTION_TICKET_TYPE, "no", True, False),
# out of hours, if the user says its not an emergency
(PROBLEM_TICKET_TYPE, "no", False, False),
(QUESTION_TICKET_TYPE, "no", False, False),
# out of hours, only problems can be emergencies
(PROBLEM_TICKET_TYPE, "yes", False, True),
(QUESTION_TICKET_TYPE, "yes", False, False),
],
)
def test_urgency(
client_request,
mock_get_non_empty_organizations_and_services_for_user,
mocker,
ticket_type,
severe,
is_in_business_hours,
is_out_of_hours_emergency,
):
mocker.patch(
"app.main.views.feedback.in_business_hours", return_value=is_in_business_hours
)
mock_ticket = mocker.patch("app.main.views.feedback.NotifySupportTicket")
mocker.patch(
"app.main.views.feedback.zendesk_client.send_ticket_to_zendesk",
autospec=True,
)
client_request.post(
"main.feedback",
ticket_type=ticket_type,
severe=severe,
_data={"feedback": "blah", "email_address": "test@example.com"},
_expected_status=302,
_expected_redirect=url_for(
"main.thanks",
out_of_hours_emergency=is_out_of_hours_emergency,
email_address_provided=True,
),
)
assert mock_ticket.call_args[1]["p1"] == is_out_of_hours_emergency
ids, params = zip(
*[
(
"non-logged in users always have to triage",
(
GENERAL_TICKET_TYPE,
False,
False,
True,
302,
partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE),
),
),
(
"trial services are never high priority",
(PROBLEM_TICKET_TYPE, False, True, False, 200, no_redirect()),
),
(
"we can triage in hours",
(PROBLEM_TICKET_TYPE, True, True, True, 200, no_redirect()),
),
(
"only problems are high priority",
(QUESTION_TICKET_TYPE, False, True, True, 200, no_redirect()),
),
(
"should triage out of hours",
(
PROBLEM_TICKET_TYPE,
False,
True,
True,
302,
partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
),
),
]
)
@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_request,
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.models.user.User.live_services",
new_callable=PropertyMock,
return_value=[{}, {}] if has_live_services else [],
)
mocker.patch(
"app.main.views.feedback.in_business_hours", return_value=is_in_business_hours
)
if not logged_in:
client_request.logout()
client_request.get(
"main.feedback",
ticket_type=ticket_type,
_expected_status=expected_status,
_expected_redirect=expected_redirect(),
)
@pytest.mark.parametrize(
("ticket_type", "expected_h1"),
[
(PROBLEM_TICKET_TYPE, "Report a problem"),
(GENERAL_TICKET_TYPE, "Contact Notify.gov support"),
],
)
def test_options_on_triage_page(
client_request,
ticket_type,
expected_h1,
):
page = client_request.get("main.triage", ticket_type=ticket_type)
assert normalize_spaces(page.select_one("h1").text) == expected_h1
assert page.select("form input[type=radio]")[0]["value"] == "yes"
assert page.select("form input[type=radio]")[1]["value"] == "no"
def test_doesnt_lose_message_if_post_across_closing(
client_request,
mocker,
):
mocker.patch("app.models.user.User.live_services", return_value=True)
mocker.patch("app.main.views.feedback.in_business_hours", return_value=False)
page = client_request.post(
"main.feedback",
ticket_type=PROBLEM_TICKET_TYPE,
_data={"feedback": "foo"},
_expected_status=302,
_expected_redirect=url_for(".triage", ticket_type=PROBLEM_TICKET_TYPE),
)
with client_request.session_transaction() as session:
assert session["feedback_message"] == "foo"
page = client_request.get(
"main.feedback",
ticket_type=PROBLEM_TICKET_TYPE,
severe="yes",
)
with client_request.session_transaction() as session:
assert page.find("textarea", {"name": "feedback"}).text == "\r\nfoo"
assert "feedback_message" not in session
@pytest.mark.parametrize(
("when", "is_in_business_hours"),
[
("2016-06-06 09:29:59+0100", False), # opening time, summer and winter
("2016-12-12 09:29:59+0000", False),
("2016-06-06 09:30:00+0100", True),
("2016-12-12 09:30:00+0000", True),
("2016-12-12 12:00:00+0000", True), # middle of the day
("2016-12-12 17:29:59+0000", True), # closing time
("2016-12-12 17:30:00+0000", False),
("2016-12-10 12:00:00+0000", False), # Saturday
("2016-12-11 12:00:00+0000", False), # Sunday
("2016-01-01 12:00:00+0000", 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(
"ticket_type",
[
GENERAL_TICKET_TYPE,
PROBLEM_TICKET_TYPE,
],
)
@pytest.mark.parametrize(
("choice", "expected_redirect_param"),
[
("yes", "yes"),
("no", "no"),
],
)
def test_triage_redirects_to_correct_url(
client_request,
ticket_type,
choice,
expected_redirect_param,
):
client_request.post(
"main.triage",
ticket_type=ticket_type,
_data={"severe": choice},
_expected_status=302,
_expected_redirect=url_for(
"main.feedback",
ticket_type=ticket_type,
severe=expected_redirect_param,
),
)
@pytest.mark.parametrize(
("extra_args", "expected_back_link"),
[
(
{"severe": "yes"},
partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
),
(
{"severe": "no"},
partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
),
({"severe": "foo"}, partial(url_for, "main.support")), # hacking the URL
({}, partial(url_for, "main.support")),
],
)
@freeze_time("2012-12-12 12:12")
def test_back_link_from_form(
client_request,
mock_get_non_empty_organizations_and_services_for_user,
extra_args,
expected_back_link,
):
page = client_request.get(
"main.feedback", ticket_type=PROBLEM_TICKET_TYPE, **extra_args
)
assert page.select_one(".usa-back-link")["href"] == expected_back_link()
assert normalize_spaces(page.select_one("h1").text) == "Report a problem"
@pytest.mark.parametrize(
(
"is_in_business_hours",
"severe",
"expected_status_code",
"expected_redirect",
"expected_status_code_when_logged_in",
"expected_redirect_when_logged_in",
),
[
(True, "yes", 200, no_redirect(), 200, no_redirect()),
(True, "no", 200, no_redirect(), 200, no_redirect()),
(
False,
"no",
200,
no_redirect(),
200,
no_redirect(),
),
# Treat empty query param as mangled URL ask question again
(
False,
"",
302,
partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
302,
partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
),
# User hasnt answered the triage question
(
False,
None,
302,
partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
302,
partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
),
# Escalation is needed for non-logged-in users
(
False,
"yes",
302,
partial(url_for, "main.bat_phone"),
200,
no_redirect(),
),
],
)
def test_should_be_shown_the_bat_email(
client_request,
active_user_with_permissions,
mocker,
service_one,
mock_get_non_empty_organizations_and_services_for_user,
is_in_business_hours,
severe,
expected_status_code,
expected_redirect,
expected_status_code_when_logged_in,
expected_redirect_when_logged_in,
):
mocker.patch(
"app.main.views.feedback.in_business_hours", return_value=is_in_business_hours
)
feedback_page = url_for(
"main.feedback", ticket_type=PROBLEM_TICKET_TYPE, severe=severe
)
client_request.logout()
client_request.get_url(
feedback_page,
_expected_status=expected_status_code,
_expected_redirect=expected_redirect(),
)
# logged in users should never be redirected to the bat email page
client_request.login(active_user_with_permissions)
client_request.get_url(
feedback_page,
_expected_status=expected_status_code_when_logged_in,
_expected_redirect=expected_redirect_when_logged_in(),
)
@pytest.mark.parametrize(
(
"severe",
"expected_status_code",
"expected_redirect",
"expected_status_code_when_logged_in",
"expected_redirect_when_logged_in",
),
[
# User hasnt answered the triage question
(
None,
302,
partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE),
302,
partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE),
),
# Escalation is needed for non-logged-in users
(
"yes",
302,
partial(url_for, "main.bat_phone"),
200,
no_redirect(),
),
],
)
def test_should_be_shown_the_bat_email_for_general_questions(
client_request,
active_user_with_permissions,
mocker,
service_one,
mock_get_non_empty_organizations_and_services_for_user,
severe,
expected_status_code,
expected_redirect,
expected_status_code_when_logged_in,
expected_redirect_when_logged_in,
):
mocker.patch("app.main.views.feedback.in_business_hours", return_value=False)
feedback_page = url_for(
"main.feedback", ticket_type=GENERAL_TICKET_TYPE, severe=severe
)
client_request.logout()
client_request.get_url(
feedback_page,
_expected_status=expected_status_code,
_expected_redirect=expected_redirect(),
)
# logged in users should never be redirected to the bat email page
client_request.login(active_user_with_permissions)
client_request.get_url(
feedback_page,
_expected_status=expected_status_code_when_logged_in,
_expected_redirect=expected_redirect_when_logged_in(),
)
def test_bat_email_page(
client_request,
active_user_with_permissions,
mocker,
service_one,
):
bat_phone_page = "main.bat_phone"
client_request.logout()
page = client_request.get(bat_phone_page)
assert page.select_one(".usa-back-link").text == "Back"
assert page.select_one(".usa-back-link")["href"] == url_for("main.support")
assert page.select("main a")[1].text == "Fill in this form"
assert page.select("main a")[1]["href"] == url_for(
"main.feedback", ticket_type=PROBLEM_TICKET_TYPE, severe="no"
)
next_page = client_request.get_url(page.select("main a")[1]["href"])
assert next_page.h1.text.strip() == "Report a problem"
client_request.login(active_user_with_permissions)
client_request.get(
bat_phone_page,
_expected_redirect=url_for("main.feedback", ticket_type=PROBLEM_TICKET_TYPE),
)
@pytest.mark.parametrize(
("out_of_hours_emergency", "email_address_provided", "out_of_hours", "message"),
[
# Out of hours emergencies trump everything else
(
True,
True,
True,
"Well reply in the next 30 minutes.",
),
(
True,
False,
False, # Not a real scenario
"Well reply in the next 30 minutes.",
),
# Anonymous tickets dont promise a reply
(
False,
False,
False,
"Well aim to read your message in the next 30 minutes.",
),
(
False,
False,
True,
"Well read your message when were back in the office.",
),
# When we look at your ticket depends on whether were in normal
# business hours
(
False,
True,
False,
"Well aim to read your message in the next 30 minutes and well reply within one working day.",
),
(False, True, True, "Well reply within one working day."),
],
)
def test_thanks(
client_request,
mocker,
api_user_active,
mock_get_user,
out_of_hours_emergency,
email_address_provided,
out_of_hours,
message,
):
mocker.patch(
"app.main.views.feedback.in_business_hours", return_value=(not out_of_hours)
)
page = client_request.get(
"main.thanks",
out_of_hours_emergency=out_of_hours_emergency,
email_address_provided=email_address_provided,
)
assert normalize_spaces(page.find("main").find("p").text) == message
assert normalize_spaces(page.select("h1")) == ("Contact us")
-8
View File
@@ -65,14 +65,6 @@ def test_robots(client_request):
("endpoint", "kwargs"),
[
("sign_in", {}),
("support", {}),
("support_public", {}),
("triage", {}),
("feedback", {"ticket_type": "ask-question-give-feedback"}),
("feedback", {"ticket_type": "general"}),
("feedback", {"ticket_type": "report-problem"}),
("bat_phone", {}),
("thanks", {}),
("register", {}),
pytest.param("index", {}, marks=pytest.mark.xfail(raises=AssertionError)),
],
-1
View File
@@ -10,7 +10,6 @@ def test_anonymous_user(notify_admin):
assert AnonymousUser().default_organization.name is None
assert AnonymousUser().default_organization.domains == []
assert AnonymousUser().default_organization.organization_type is None
assert AnonymousUser().default_organization.request_to_go_live_notes is None
def test_user(notify_admin):
-9
View File
@@ -31,7 +31,6 @@ EXCLUDED_ENDPOINTS = tuple(
"api_keys",
"archive_service",
"archive_user",
"bat_phone",
"begin_tour",
"billing_details",
"callbacks",
@@ -73,7 +72,6 @@ EXCLUDED_ENDPOINTS = tuple(
"edit_data_retention",
"edit_organization_billing_details",
"edit_organization_domains",
"edit_organization_go_live_notes",
"edit_organization_name",
"edit_organization_notes",
"edit_organization_type",
@@ -86,10 +84,8 @@ EXCLUDED_ENDPOINTS = tuple(
"edit_user_permissions",
"email_not_received",
"error",
"estimate_usage",
"features",
"features_sms",
"feedback",
"find_services_by_name",
"find_users_by_email",
"forgot_password",
@@ -153,7 +149,6 @@ EXCLUDED_ENDPOINTS = tuple(
"registration_continue",
"remove_user_from_organization",
"remove_user_from_service",
"request_to_go_live",
"resend_email_link",
"resend_email_verification",
"resume_service",
@@ -205,16 +200,12 @@ EXCLUDED_ENDPOINTS = tuple(
"sign_in",
"sign_out",
"start_job",
"submit_request_to_go_live",
"support",
"support_public",
"suspend_service",
"template_history",
"template_usage",
"terms",
"thanks",
"tour_step",
"triage",
"trial_mode",
"trial_mode_new",
"trial_services",