mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-23 07:46:23 -04:00
Merge branch 'main' of https://github.com/GSA/notifications-admin into 1043-move-account-info-into-utility-nav
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -479,3 +479,7 @@ details form {
|
||||
padding-left: 5px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.edit-textbox-error-mt {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ EVENT_SCHEMAS = {
|
||||
"service_id",
|
||||
"ui_permissions",
|
||||
},
|
||||
"resend_user_invite_to_service": {"email_address", "resent_by_id", "service_id"},
|
||||
"cancel_user_invite_to_service": {"email_address", "canceled_by_id", "service_id"},
|
||||
"set_user_permissions": {
|
||||
"user_id",
|
||||
@@ -63,6 +64,10 @@ def create_cancel_user_invite_to_service_event(**kwargs):
|
||||
_send_event("cancel_user_invite_to_service", **kwargs)
|
||||
|
||||
|
||||
def create_resend_user_invite_to_service_event(**kwargs):
|
||||
_send_event("resend_user_invite_to_service", **kwargs)
|
||||
|
||||
|
||||
def create_add_user_to_service_event(**kwargs):
|
||||
_send_event("add_user_to_service", **kwargs)
|
||||
|
||||
|
||||
@@ -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", "I’m 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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -9,6 +9,7 @@ from app.event_handlers import (
|
||||
create_invite_user_to_service_event,
|
||||
create_mobile_number_change_event,
|
||||
create_remove_user_from_service_event,
|
||||
create_resend_user_invite_to_service_event,
|
||||
)
|
||||
from app.formatters import redact_mobile_number
|
||||
from app.main import main
|
||||
@@ -331,3 +332,22 @@ def cancel_invited_user(service_id, invited_user_id):
|
||||
|
||||
flash(f"Invitation cancelled for {invited_user.email_address}", "default_with_tick")
|
||||
return redirect(url_for("main.manage_users", service_id=service_id))
|
||||
|
||||
|
||||
@main.route(
|
||||
"/services/<uuid:service_id>/resend-invite/<uuid:invited_user_id>",
|
||||
methods=["GET"],
|
||||
)
|
||||
@user_has_permissions("manage_service")
|
||||
def resend_invite(service_id, invited_user_id):
|
||||
current_service.resend_invite(invited_user_id)
|
||||
|
||||
invited_user = InvitedUser.by_id_and_service_id(service_id, invited_user_id)
|
||||
create_resend_user_invite_to_service_event(
|
||||
email_address=invited_user.email_address,
|
||||
resent_by_id=current_user.id,
|
||||
service_id=service_id,
|
||||
)
|
||||
|
||||
flash(f"Invitation resent for {invited_user.email_address}", "default_with_tick")
|
||||
return redirect(url_for("main.manage_users", service_id=service_id))
|
||||
|
||||
@@ -3,6 +3,7 @@ from datetime import datetime
|
||||
|
||||
from flask import (
|
||||
Response,
|
||||
flash,
|
||||
jsonify,
|
||||
render_template,
|
||||
request,
|
||||
@@ -32,14 +33,16 @@ from app.utils.user import user_has_permissions
|
||||
|
||||
@main.route("/services/<uuid:service_id>/notification/<uuid:notification_id>")
|
||||
@user_has_permissions("view_activity", "send_messages")
|
||||
def view_notification(service_id, notification_id):
|
||||
def view_notification(service_id, notification_id, error_message=None):
|
||||
if error_message:
|
||||
flash(error_message)
|
||||
|
||||
notification = notification_api_client.get_notification(
|
||||
service_id, str(notification_id)
|
||||
)
|
||||
notification["template"].update({"reply_to_text": notification["reply_to_text"]})
|
||||
|
||||
personalisation = get_all_personalisation_from_notification(notification)
|
||||
error_message = None
|
||||
|
||||
template = get_template(
|
||||
notification["template"],
|
||||
|
||||
@@ -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,17 +1,10 @@
|
||||
import itertools
|
||||
import time
|
||||
import uuid
|
||||
from string import ascii_uppercase
|
||||
from zipfile import BadZipFile
|
||||
|
||||
from flask import (
|
||||
abort,
|
||||
current_app,
|
||||
flash,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from flask import abort, flash, redirect, render_template, request, session, url_for
|
||||
from flask_login import current_user
|
||||
from notifications_python_client.errors import HTTPError
|
||||
from notifications_utils import SMS_CHAR_COUNT_LIMIT
|
||||
@@ -495,7 +488,6 @@ def _check_messages(service_id, template_id, upload_id, preview_row):
|
||||
remaining_messages = current_service.message_limit - notification_count
|
||||
|
||||
contents = s3download(service_id, upload_id)
|
||||
|
||||
db_template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
)
|
||||
@@ -836,7 +828,6 @@ def get_template_error_dict(exception):
|
||||
@user_has_permissions("send_messages", restrict_admin_usage=True)
|
||||
def send_notification(service_id, template_id):
|
||||
recipient = get_recipient()
|
||||
|
||||
if not recipient:
|
||||
return redirect(
|
||||
url_for(
|
||||
@@ -846,38 +837,69 @@ def send_notification(service_id, template_id):
|
||||
)
|
||||
)
|
||||
|
||||
db_template = current_service.get_template_with_user_permission_or_403(
|
||||
template_id, current_user
|
||||
keys = []
|
||||
values = []
|
||||
for k, v in session["placeholders"].items():
|
||||
keys.append(k)
|
||||
values.append(v)
|
||||
|
||||
data = ",".join(keys)
|
||||
vals = ",".join(values)
|
||||
data = f"{data}\r\n{vals}"
|
||||
|
||||
filename = f"one-off-{current_user.name}-{uuid.uuid4()}.csv"
|
||||
my_data = {"filename": filename, "template_id": template_id, "data": data}
|
||||
upload_id = s3upload(service_id, my_data)
|
||||
form = CsvUploadForm()
|
||||
form.file.data = my_data
|
||||
form.file.name = filename
|
||||
|
||||
check_message_output = check_messages(service_id, template_id, upload_id, 2)
|
||||
if "You cannot send to" in check_message_output:
|
||||
return check_messages(service_id, template_id, upload_id, 2)
|
||||
|
||||
job_api_client.create_job(
|
||||
upload_id,
|
||||
service_id,
|
||||
scheduled_for="",
|
||||
template_id=template_id,
|
||||
original_file_name=filename,
|
||||
notification_count=1,
|
||||
valid="True",
|
||||
)
|
||||
|
||||
try:
|
||||
noti = notification_api_client.send_notification(
|
||||
service_id,
|
||||
template_id=db_template["id"],
|
||||
recipient=recipient,
|
||||
personalisation=session["placeholders"],
|
||||
sender_id=session.get("sender_id", None),
|
||||
session.pop("recipient")
|
||||
session.pop("placeholders")
|
||||
|
||||
# We have to wait for the job to run and create the notification in the database
|
||||
time.sleep(0.1)
|
||||
notifications = notification_api_client.get_notifications_for_service(
|
||||
service_id, job_id=upload_id, include_one_off=True
|
||||
)
|
||||
attempts = 0
|
||||
while notifications["total"] == 0 and attempts < 5:
|
||||
notifications = notification_api_client.get_notifications_for_service(
|
||||
service_id, job_id=upload_id, include_one_off=True
|
||||
)
|
||||
except HTTPError as exception:
|
||||
current_app.logger.error(
|
||||
'Service {} could not send notification: "{}"'.format(
|
||||
current_service.id, exception.message
|
||||
time.sleep(0.1)
|
||||
attempts = attempts + 1
|
||||
|
||||
if notifications["total"] == 0 and attempts == 5:
|
||||
# This shows the job we auto-generated for the user
|
||||
return redirect(
|
||||
url_for(
|
||||
"main.view_job",
|
||||
service_id=service_id,
|
||||
job_id=upload_id,
|
||||
)
|
||||
)
|
||||
return render_template(
|
||||
"views/notifications/check.html",
|
||||
**_check_notification(service_id, template_id, exception),
|
||||
)
|
||||
|
||||
session.pop("placeholders")
|
||||
session.pop("recipient")
|
||||
session.pop("sender_id", None)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
".view_notification",
|
||||
service_id=service_id,
|
||||
notification_id=noti["id"],
|
||||
from_job=upload_id,
|
||||
notification_id=notifications["notifications"][0]["id"],
|
||||
# used to show the final step of the tour (help=3) or not show
|
||||
# a back link on a just sent one off notification (help=0)
|
||||
help=request.args.get("help"),
|
||||
|
||||
@@ -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. We’ll 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"]
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
QUESTION_TICKET_TYPE = "ask-question-give-feedback"
|
||||
PROBLEM_TICKET_TYPE = "report-problem"
|
||||
GENERAL_TICKET_TYPE = "general"
|
||||
@@ -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):
|
||||
|
||||
@@ -190,6 +190,15 @@ class Service(JSONModel, SortByNameMixin):
|
||||
invited_user_id=str(invited_user_id),
|
||||
)
|
||||
|
||||
def resend_invite(self, invited_user_id):
|
||||
if str(invited_user_id) not in {user.id for user in self.invited_users}:
|
||||
abort(404)
|
||||
|
||||
return invite_api_client.resend_invite(
|
||||
service_id=self.id,
|
||||
invited_user_id=str(invited_user_id),
|
||||
)
|
||||
|
||||
def get_team_member(self, user_id):
|
||||
if str(user_id) not in {user.id for user in self.active_users}:
|
||||
abort(404)
|
||||
@@ -370,22 +379,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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -32,11 +32,11 @@ class InviteApiClient(NotifyAdminAPIClient):
|
||||
"folder_permissions": folder_permissions,
|
||||
}
|
||||
data = _attach_current_user(data)
|
||||
resp = self.post(url="/service/{}/invite".format(service_id), data=data)
|
||||
resp = self.post(url=f"/service/{service_id}/invite", data=data)
|
||||
return resp["data"]
|
||||
|
||||
def get_invites_for_service(self, service_id):
|
||||
return self.get("/service/{}/invite".format(service_id))["data"]
|
||||
return self.get(f"/service/{service_id}/invite")["data"]
|
||||
|
||||
def get_invited_user(self, invited_user_id):
|
||||
return self.get(f"/invite/service/{invited_user_id}")["data"]
|
||||
@@ -46,7 +46,7 @@ class InviteApiClient(NotifyAdminAPIClient):
|
||||
|
||||
def get_count_of_invites_with_permission(self, service_id, permission):
|
||||
if permission not in all_ui_permissions:
|
||||
raise TypeError("{} is not a valid permission".format(permission))
|
||||
raise TypeError(f"{permission} is not a valid permission")
|
||||
return len(
|
||||
[
|
||||
invited_user
|
||||
@@ -56,22 +56,21 @@ class InviteApiClient(NotifyAdminAPIClient):
|
||||
)
|
||||
|
||||
def check_token(self, token):
|
||||
return self.get(url="/invite/service/check/{}".format(token))["data"]
|
||||
return self.get(url=f"/invite/service/check/{token}")["data"]
|
||||
|
||||
def cancel_invited_user(self, service_id, invited_user_id):
|
||||
data = {"status": "cancelled"}
|
||||
data = _attach_current_user(data)
|
||||
self.post(
|
||||
url="/service/{0}/invite/{1}".format(service_id, invited_user_id), data=data
|
||||
)
|
||||
self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data)
|
||||
|
||||
def resend_invite(self, service_id, invited_user_id):
|
||||
self.post(url=f"/service/{service_id}/invite/{invited_user_id}/resend", data={})
|
||||
|
||||
@cache.delete("service-{service_id}")
|
||||
@cache.delete("user-{invited_user_id}")
|
||||
def accept_invite(self, service_id, invited_user_id):
|
||||
data = {"status": "accepted"}
|
||||
self.post(
|
||||
url="/service/{0}/invite/{1}".format(service_id, invited_user_id), data=data
|
||||
)
|
||||
self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data)
|
||||
|
||||
|
||||
invite_api_client = InviteApiClient()
|
||||
|
||||
@@ -103,14 +103,32 @@ class JobApiClient(NotifyAdminAPIClient):
|
||||
|
||||
return scheduled_for
|
||||
|
||||
def create_job(self, job_id, service_id, scheduled_for=None):
|
||||
def create_job(
|
||||
self,
|
||||
job_id,
|
||||
service_id,
|
||||
scheduled_for=None,
|
||||
template_id=None,
|
||||
original_file_name=None,
|
||||
notification_count=None,
|
||||
valid=None,
|
||||
):
|
||||
data = {"id": job_id}
|
||||
|
||||
# make a datetime object in the user's preferred timezone
|
||||
|
||||
if scheduled_for:
|
||||
scheduled_for = JobApiClient.convert_user_time_to_utc(scheduled_for)
|
||||
data.update({"scheduled_for": scheduled_for})
|
||||
data["scheduled_for"] = scheduled_for
|
||||
|
||||
if template_id:
|
||||
data["template_id"] = template_id
|
||||
if original_file_name:
|
||||
data["original_file_name"] = original_file_name
|
||||
if notification_count:
|
||||
data["notification_count"] = notification_count
|
||||
if valid:
|
||||
data["valid"] = valid
|
||||
|
||||
data = _attach_current_user(data)
|
||||
job = self.post(url="/service/{}/job".format(service_id), data=data)
|
||||
|
||||
@@ -19,17 +19,22 @@
|
||||
class="form-group{% if field.errors %} form-group-error{% endif %} {{ extra_form_group_classes }}"
|
||||
data-module="{% if autofocus %}autofocus{% elif colour_preview %}colour-preview{% endif %}"
|
||||
>
|
||||
{% if field.errors %}
|
||||
<div class="usa-alert usa-alert--error edit-textbox-error-mt" role="alert">
|
||||
<div class="usa-alert__body">
|
||||
<h4 class="usa-alert__heading">Error message</h4>
|
||||
<p class="usa-alert__text" data-module="track-error" data-error-type="{{ field.errors[0] }}" data-error-label="{{ field.name }}">
|
||||
{% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<label class="usa-label" for="{{ field.name }}">
|
||||
{% if label %}
|
||||
{{ label }}
|
||||
{% else %}
|
||||
{{ field.label.text }}
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<span class="error-message" data-module="track-error" data-error-type="{{ field.errors[0] }}" data-error-label="{{ field.name }}">
|
||||
{% if not safe_error_message %}{{ field.errors[0] }}{% else %}{{ field.errors[0]|safe }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% if hint %}
|
||||
<div class="usa-hint">
|
||||
|
||||
@@ -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 }}
|
||||
Requester’s 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 %}
|
||||
@@ -10,7 +10,12 @@ Error
|
||||
{% endblock %}
|
||||
|
||||
{% block backLink %}
|
||||
{{ usaBackLink({ "href": back_link }) }}
|
||||
<!--hide back link for one-off sends because the user never created a csv file-->
|
||||
{% if recipients|length == 1 and not recipients.allowed_to_send_to and not recipients.missing_column_headers %}
|
||||
<!--do nothing-->
|
||||
{% else %}
|
||||
{{ usaBackLink({ "href": back_link }) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
@@ -130,7 +135,10 @@ Error
|
||||
{% endcall %}
|
||||
</div>
|
||||
|
||||
|
||||
<!--hide the upload button and back to top link for one off sends-->
|
||||
{% if recipients|length == 1 and not recipients.allowed_to_send_to and not recipients.missing_column_headers %}
|
||||
<!-- do nothing -->
|
||||
{% else %}
|
||||
<div class="js-stick-at-top-when-scrolling">
|
||||
<div class="form-group">
|
||||
{% if not request.args.from_test %}
|
||||
@@ -144,6 +152,7 @@ Error
|
||||
</div>
|
||||
<a href="#content" class="usa-link back-to-top-link">Back to top</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not request.args.from_test %}
|
||||
|
||||
@@ -210,4 +219,4 @@ recipients.column_headers %}
|
||||
<h2 class="font-body-lg">Preview of {{ template.name }}</h2>
|
||||
{{ template|string }}
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
{% if not current_user.is_authenticated or not current_service %}
|
||||
<p>When you’re 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>. We’ll 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 you’re ready to send messages to people outside your team. We’ll 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 you’re ready to send messages to people outside your team. We’ll 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 you’re planning to exceed the <a class="usa-link" href="{{ url_for('.pricing', _anchor='text-messages') }}">free text message allowance</a>.</p> -->
|
||||
</li>
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span> (invited)
|
||||
{%- elif user.status == 'cancelled' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span> (cancelled invite)
|
||||
{%- elif user.status == 'expired' -%}
|
||||
<span class="live-search-relevant">{{ user.email_address }}</span> (expired invite)
|
||||
{%- elif user.id == current_user.id -%}
|
||||
<span class="live-search-relevant">(you)</span>
|
||||
{% else %}
|
||||
@@ -84,6 +86,8 @@
|
||||
{% if current_user.has_permissions('manage_service') %}
|
||||
{% if user.status == 'pending' %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.cancel_invited_user', service_id=current_service.id, invited_user_id=user.id)}}">Cancel invitation<span class="usa-sr-only"> for {{ user.email_address }}</span></a>
|
||||
{% elif user.status == 'expired' %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.resend_invite', service_id=current_service.id, invited_user_id=user.id)}}">Resend invite<span class="usa-sr-only"> for {{ user.email_address }}</span></a>
|
||||
{% elif user.is_editable_by(current_user) %}
|
||||
<a class="user-list-edit-link usa-link" href="{{ url_for('.edit_user_permissions', service_id=current_service.id, user_id=user.id)}}">Change details<span class="usa-sr-only"> for {{ user.name }} {{ user.email_address }}</span></a>
|
||||
{% endif %}
|
||||
|
||||
@@ -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')}}
|
||||
|
||||
@@ -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 we’ll get back to you within one working day.
|
||||
</p>
|
||||
<p class="bottom-gutter">
|
||||
By requesting to go live you’re 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 you’re 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>
|
||||
We’ll reply within 30 minutes and give you hourly updates
|
||||
until the problem’s 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 we’ll get back to you by the next working day.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
@@ -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>We’ll reply to {{ current_user.email_address }}</p>
|
||||
{% endif %}
|
||||
{{ sticky_page_footer('Send') }}
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
We’ll reply in the next 30 minutes.
|
||||
{% else %}
|
||||
{% if email_address_provided %}
|
||||
{% if out_of_hours %}
|
||||
We’ll reply within one working day.
|
||||
{% else %}
|
||||
We’ll aim to read your message in the next 30 minutes and we’ll reply within one
|
||||
working day.
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if out_of_hours %}
|
||||
We’ll read your message when we’re back in the office.
|
||||
{% else %}
|
||||
We’ll aim to read your message in the next 30 minutes.
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
@@ -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">
|
||||
It’s 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">
|
||||
It’s 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 %}
|
||||
@@ -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:
|
||||
@@ -38,6 +38,6 @@
|
||||
<li>update your settings so you’re ready to send and receive messages</li>
|
||||
<li>accept our terms of use</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -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]))"
|
||||
|
||||
Reference in New Issue
Block a user