merge from main and code review feedback

This commit is contained in:
Kenneth Kehl
2024-03-13 10:17:38 -07:00
58 changed files with 1293 additions and 503 deletions

View File

@@ -48,13 +48,13 @@
}
}
.sms-message-sender {
margin: units(1) 0 0;
.sms-message-sender, .sms-message-file-name, .sms-message-scheduler, .sms-message-template, .sms-message-sender {
margin:0.25rem 0 0;
}
.sms-message-recipient {
color: color('gray-cool-90');
margin: 0 0 units(1);
margin: units(1) 0 units(1);
}
.sms-message-status {
@@ -131,7 +131,7 @@
&-label,
&-button-label {
font-weight: bold;
font-size: 19px;
font-size: 19px;
display: block;
margin: 0 0 10px 0;
}

View File

@@ -26,7 +26,7 @@ i.e.
.usa-logo {
font-family: family("sans");
margin: units(4) 0;
@include at-media-max('mobile-lg') {
@include at-media-max('desktop') {
margin: units(4) 0 units(4) units(2);
}
img {
@@ -43,6 +43,16 @@ i.e.
text-decoration: underline;
}
}
@include at-media-max('desktop') {
padding: 0 units(2);
ul li {
padding-bottom: units(1);
}
}
}
.usa-nav-container {
max-width: 100%;
padding: 0;
}
}
@@ -345,6 +355,9 @@ td.table-empty-message {
background-image: url(../img/material-icons/description.svg);
}
}
.table-wrapper {
overflow-x: scroll;
}
}
.dashboard-table {
@@ -403,8 +416,11 @@ td.table-empty-message {
width: 5%;
}
th {
padding: 0.5rem 0.5rem;
padding: 0.5rem 1rem
}
td {
padding: 0.5rem 1rem
}
}
#template-list {

View File

@@ -36,7 +36,7 @@ class Config(object):
NR_BROWSER_KEY = getenv("NR_BROWSER_KEY")
settings = newrelic.agent.global_settings()
NR_MONITOR_ON = settings and settings.monitor_mode
COMMIT_HASH = getenv("COMMIT_HASH", "Unknown")
COMMIT_HASH = getenv("COMMIT_HASH", "-----")
TEMPLATE_PREVIEW_API_HOST = getenv(
"TEMPLATE_PREVIEW_API_HOST", "http://localhost:9999"

View File

@@ -1226,7 +1226,11 @@ class CsvUploadForm(StripWhitespaceForm):
validators=[
DataRequired(message="Please pick a file"),
CsvFileValidator(),
FileSize(max_size=10e6, message="File must be smaller than 10Mb"), # 10Mb
FileSize(
max_size=10e6,
message="File must be smaller than 10Mb. If you are trying to upload an Excel file, \
please export the contents in the CSV format and then try again.",
), # 10Mb
],
)

View File

@@ -76,6 +76,7 @@ def service_dashboard(service_id):
"notifications": aggregate_notifications_by_job.get(job["id"], []),
}
for job in job_response
if aggregate_notifications_by_job.get(job["id"], [])
]
return render_template(
"views/dashboard/dashboard.html",

View File

@@ -102,15 +102,6 @@ def security():
return render_template("views/security.html", navigation_links=features_nav())
@main.route("/features/terms", endpoint="terms")
@user_is_logged_in
def terms():
return render_template(
"views/terms-of-use.html",
navigation_links=features_nav(),
)
@main.route("/features/using_notify")
@user_is_logged_in
def using_notify():
@@ -214,7 +205,6 @@ def send_files_by_email():
@main.route("/roadmap", endpoint="old_roadmap")
@main.route("/terms", endpoint="old_terms")
@main.route("/information-security", endpoint="information_security")
@main.route("/using_notify", endpoint="old_using_notify")
@main.route("/information-risk-management", endpoint="information_risk_management")
@@ -222,7 +212,6 @@ def send_files_by_email():
def old_page_redirects():
redirects = {
"main.old_roadmap": "main.roadmap",
"main.old_terms": "main.terms",
"main.information_security": "main.using_notify",
"main.old_using_notify": "main.using_notify",
"main.information_risk_management": "main.security",

View File

@@ -276,12 +276,16 @@ def get_status_filters(service, message_type, statistics):
}
else:
stats = statistics[message_type]
stats["sending"] = stats["requested"] - stats["delivered"] - stats["failed"]
if stats.get("failure") is not None:
stats["failed"] = stats["failure"]
stats["pending"] = stats["requested"] - stats["delivered"] - stats["failed"]
filters = [
# key, label, option
("requested", "total", "sending,delivered,failed"),
("sending", "pending", "pending"),
("pending", "pending", "pending"),
("delivered", "delivered", "delivered"),
("failed", "failed", "failed"),
]
@@ -296,7 +300,7 @@ def get_status_filters(service, message_type, statistics):
message_type=message_type,
status=option,
),
stats[key],
stats.get(key),
)
for key, label, option in filters
]

View File

@@ -6,6 +6,7 @@ from zipfile import BadZipFile
from flask import abort, flash, redirect, render_template, request, session, url_for
from flask_login import current_user
from markupsafe import Markup
from notifications_python_client.errors import HTTPError
from notifications_utils import SMS_CHAR_COUNT_LIMIT
from notifications_utils.insensitive_dict import InsensitiveDict
@@ -151,8 +152,11 @@ def send_messages(service_id, template_id):
# just show the first error, as we don't expect the form to have more
# than one, since it only has one field
first_field_errors = list(form.errors.values())[0]
flash(first_field_errors[0])
error_message = '<span class="usa-error-message">'
error_message = f"{error_message}{first_field_errors[0]}"
error_message = f"{error_message}</span>"
error_message = Markup(error_message)
flash(error_message)
column_headings = get_spreadsheet_column_headings_from_template(template)
return render_template(
@@ -504,13 +508,18 @@ def _check_messages(service_id, template_id, upload_id, preview_row):
template = get_template(
db_template,
current_service,
show_recipient=True,
show_recipient=False,
email_reply_to=email_reply_to,
sms_sender=sms_sender,
)
simplifed_template = get_template(
db_template,
current_service,
show_recipient=False,
)
recipients = RecipientCSV(
contents,
template=template,
template=template or simplifed_template,
max_initial_rows_shown=50,
max_errors_shown=50,
guestlist=(
@@ -530,11 +539,20 @@ def _check_messages(service_id, template_id, upload_id, preview_row):
back_link = url_for(
"main.send_one_off", service_id=service_id, template_id=template.id
)
back_link_from_preview = url_for(
"main.send_one_off", service_id=service_id, template_id=template.id
)
choose_time_form = None
else:
back_link = url_for(
"main.send_messages", service_id=service_id, template_id=template.id
)
back_link_from_preview = url_for(
"main.check_messages",
service_id=service_id,
template_id=template.id,
upload_id=upload_id,
)
choose_time_form = ChooseTimeForm()
if preview_row < 2:
@@ -542,6 +560,7 @@ def _check_messages(service_id, template_id, upload_id, preview_row):
if preview_row < len(recipients) + 2:
template.values = recipients[preview_row - 2].recipient_and_personalisation
simplifed_template.values = recipients[preview_row - 2].recipient_and_personalisation
elif preview_row > 2:
abort(404)
@@ -562,11 +581,14 @@ def _check_messages(service_id, template_id, upload_id, preview_row):
remaining_messages=remaining_messages,
choose_time_form=choose_time_form,
back_link=back_link,
back_link_from_preview=back_link_from_preview,
first_recipient_column=recipients.recipient_column_headers[0],
preview_row=preview_row,
sent_previously=job_api_client.has_sent_previously(
service_id, template.id, db_template["version"], original_file_name
),
template_id=template_id,
simplifed_template=simplifed_template,
)
@@ -614,13 +636,34 @@ def check_messages(service_id, template_id, upload_id, row_index=2):
return render_template("views/check/ok.html", **data)
@main.route(
"/services/<uuid:service_id>/<uuid:template_id>/check/<uuid:upload_id>/preview",
methods=["POST"],
)
@main.route(
"/services/<uuid:service_id>/<uuid:template_id>/check/<uuid:upload_id>/preview/row-<int:row_index>",
methods=["POST"],
)
@user_has_permissions("send_messages", restrict_admin_usage=True)
def preview_job(service_id, template_id, upload_id, row_index=2):
session["scheduled_for"] = request.form.get("scheduled_for", "")
data = _check_messages(service_id, template_id, upload_id, row_index)
return render_template(
"views/check/preview.html",
scheduled_for=session["scheduled_for"],
**data,
)
@main.route("/services/<uuid:service_id>/start-job/<uuid:upload_id>", methods=["POST"])
@user_has_permissions("send_messages", restrict_admin_usage=True)
def start_job(service_id, upload_id):
scheduled_for = session.pop("scheduled_for", None)
job_api_client.create_job(
upload_id,
service_id,
scheduled_for=request.form.get("scheduled_for", ""),
scheduled_for=scheduled_for,
)
session.pop("sender_id", None)
@@ -679,7 +722,20 @@ def get_send_test_page_title(template_type, entering_recipient, name=None):
return "Personalize this message"
def get_back_link(service_id, template, step_index, placeholders=None):
def get_back_link(
service_id,
template,
step_index,
placeholders=None,
preview=False,
):
if preview:
return url_for(
"main.check_notification",
service_id=service_id,
template_id=template.id,
)
if step_index == 0:
if should_skip_template_page(template._template):
return url_for(
@@ -779,11 +835,18 @@ def _check_notification(service_id, template_id, exception=None):
email_reply_to=email_reply_to,
sms_sender=sms_sender,
)
simplifed_template = get_template(
db_template,
current_service,
)
placeholders = fields_to_fill_in(template)
back_link = get_back_link(service_id, template, len(placeholders), placeholders)
back_link_from_preview = get_back_link(
service_id, template, len(placeholders), placeholders, preview=True
)
choose_time_form = ChooseTimeForm()
if (not session.get("recipient")) or not all_placeholders_in_session(
@@ -797,8 +860,10 @@ def _check_notification(service_id, template_id, exception=None):
return dict(
template=template,
back_link=back_link,
back_link_from_preview=back_link_from_preview,
choose_time_form=choose_time_form,
**(get_template_error_dict(exception) if exception else {}),
simplifed_template=simplifed_template
)
@@ -828,12 +893,39 @@ def get_template_error_dict(exception):
}
@main.route(
"/services/<uuid:service_id>/template/<uuid:template_id>/notification/check/preview",
methods=["POST"],
)
@user_has_permissions("send_messages", restrict_admin_usage=True)
def preview_notification(service_id, template_id):
recipient = get_recipient()
if not recipient:
return redirect(
url_for(
".send_one_off",
service_id=service_id,
template_id=template_id,
)
)
session["scheduled_for"] = request.form.get("scheduled_for", "")
return render_template(
"views/notifications/preview.html",
**_check_notification(service_id, template_id),
scheduled_for=session["scheduled_for"],
recipient=recipient,
)
@main.route(
"/services/<uuid:service_id>/template/<uuid:template_id>/notification/check",
methods=["POST"],
)
@user_has_permissions("send_messages", restrict_admin_usage=True)
def send_notification(service_id, template_id):
scheduled_for = session.pop("scheduled_for", "")
recipient = get_recipient()
if not recipient:
return redirect(
@@ -868,7 +960,7 @@ def send_notification(service_id, template_id):
job_api_client.create_job(
upload_id,
service_id,
scheduled_for=request.form.get("scheduled_for", ""),
scheduled_for=scheduled_for,
template_id=template_id,
original_file_name=filename,
notification_count=1,

View File

@@ -53,22 +53,19 @@ def _get_access_token(code, state):
# JWT expiration time (10 minute maximum)
"exp": int(time.time()) + (10 * 60),
}
current_app.logger.warning(f"Here is the raw payload {payload}")
token = jwt.encode(payload, keystring, algorithm="RS256")
base_url = f"{access_token_url}?"
cli_assert = f"client_assertion={token}"
cli_assert_type = "client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer"
code_param = f"code={code}"
url = f"{base_url}{cli_assert}&{cli_assert_type}&{code_param}&grant_type=authorization_code"
current_app.logger.info(f"This is the url we use to get the access token: {url}")
headers = {"Authorization": "Bearer %s" % token}
response = requests.post(url, headers=headers)
current_app.logger.info(f"GOT A RESPONSE {response.json()}")
access_token = response.json()["access_token"]
return access_token
def _get_user_email(access_token):
def _get_user_email_and_uuid(access_token):
headers = {"Authorization": "Bearer %s" % access_token}
user_info_url = os.getenv("LOGIN_DOT_GOV_USER_INFO_URL")
user_attributes = requests.get(
@@ -76,7 +73,8 @@ def _get_user_email(access_token):
headers=headers,
)
user_email = user_attributes.json()["email"]
return user_email
user_uuid = user_attributes.json()["sub"]
return user_email, user_uuid
@main.route("/sign-in", methods=(["GET", "POST"]))
@@ -88,11 +86,11 @@ def sign_in():
login_gov_error = request.args.get("error")
if code and state:
access_token = _get_access_token(code, state)
user_email = _get_user_email(access_token)
user_email, user_uuid = _get_user_email_and_uuid(access_token)
redirect_url = request.args.get("next")
# activate the user
user = user_api_client.get_user_by_email(user_email)
user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
activate_user(user["id"])
return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url))
@@ -198,7 +196,7 @@ def sign_in():
form=form,
again=bool(redirect_url),
other_device=other_device,
login_gov_enabled=bool(notify_env in ["development", "staging"]),
login_gov_enabled=bool(notify_env in ["development", "staging", "demo"]),
password_reset_url=password_reset_url,
initial_signin_url=initial_signin_url,
)

View File

@@ -18,10 +18,6 @@ def features_nav():
"name": "Security",
"link": "main.security",
},
{
"name": "Terms of use",
"link": "main.terms",
},
]

View File

@@ -45,7 +45,6 @@ class HeaderNavigation(Navigation):
"features_sms",
"roadmap",
"security",
"terms",
},
"using_notify": {
"get_started",

View File

@@ -44,6 +44,16 @@ class UserApiClient(NotifyAdminAPIClient):
user_data = self.post("/user/email", data={"email": email_address})
return user_data["data"]
def get_user_by_uuid_or_email(self, user_uuid, email_address):
user_data = self.post(
"/user/get-login-gov-user",
data={"login_uuid": user_uuid, "email": email_address},
)
if user_data is None:
raise Exception("User not found")
return user_data["data"]
def get_user_by_email_or_none(self, email_address):
try:
return self.get_user_by_email(email_address)

View File

@@ -203,10 +203,6 @@
"href": url_for("main.security"),
"text": "Security"
},
{
"href": url_for("main.terms"),
"text": "Terms of use"
},
]
},
{

View File

@@ -44,61 +44,63 @@
<!-- usa header -->
<header class="usa-header usa-header--extended">
<div class="usa-navbar">
<div class="usa-logo display-flex flex-align-center" id="-logo">
{# <div class="logo-img display-flex">
<span class="usa-sr-only">US Notify Logo</span>
<image src="{{ params.assetsPath | default('/static/images') }}/us-notify-color.png" alt="US Notify logo" xlink:href=""
class="usa-flag-logo margin-right-1" width="40" height="35"></image>
</div> #}
<em class="logo-text usa-logo__text">
<a href="/" title="notify.gov">notify.gov</a>
</em>
</div>
{% if current_user.is_authenticated %}
<button type="button" class="usa-menu-btn">Menu</button>
{% endif %}
</div>
<nav aria-label="Primary navigation" class="usa-nav">
<div class="usa-nav__inner">
<button type="button" class="usa-nav__close">
<img src="/static/images/usa-icons/close.svg" role="img" alt="Close" />
</button>
<ul class="usa-nav__primary usa-accordion margin-right-1">
{% for item in params.navigation %}
{% if item.href and item.text %}
<li class="usa-nav__primary-item{{ ' is-current' if item.active }}">
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute, value in
item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
<span>{{ item.text }}</span>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
<div class="usa-nav__secondary margin-bottom-2">
<ul class="usa-nav__secondary-links">
{% for item in params.secondaryNavigation %}
{% if item.href and item.text %}
<li class="usa-nav__secondary-item{{ ' is-current' if item.active }}">
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute, value in
item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
<span>{{ item.text }}</span>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
<!-- <section aria-label="Search component">
<form class="usa-search usa-search--small margin-bottom-2" role="search">
<label class="usa-sr-only" for="search-field">Search</label>
<input class="usa-input" id="search-field" type="search" name="search" />
<button class="usa-button" type="submit">
<img src="/static/images/usa-icons-bg/search--white.svg" class="usa-search__submit-icon" alt="Search" />
</button>
</form>
</section> -->
<div class="usa-nav-container">
<div class="usa-navbar">
<div class="usa-logo display-flex flex-align-center flex-justify" id="-logo">
{# <div class="logo-img display-flex">
<span class="usa-sr-only">US Notify Logo</span>
<image src="{{ params.assetsPath | default('/static/images') }}/us-notify-color.png" alt="US Notify logo" xlink:href=""
class="usa-flag-logo margin-right-1" width="40" height="35"></image>
</div> #}
<em class="logo-text usa-logo__text">
<a href="/" title="Notify.gov">Notify.gov</a>
</em>
{% if params.navigation %}
<button type="button" class="usa-menu-btn">Menu</button>
{% endif %}
</div>
</div>
</nav>
<nav aria-label="Primary navigation" class="usa-nav">
<div class="usa-nav__inner">
<button type="button" class="usa-nav__close">
<img src="/static/images/usa-icons/close.svg" role="img" alt="Close" />
</button>
<ul class="usa-nav__primary usa-accordion margin-right-1">
{% for item in params.navigation %}
{% if item.href and item.text %}
<li class="usa-nav__primary-item{{ ' is-current' if item.active }}">
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute, value in
item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
<span>{{ item.text }}</span>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
<div class="usa-nav__secondary margin-bottom-2">
<ul class="usa-nav__secondary-links">
{% for item in params.secondaryNavigation %}
{% if item.href and item.text %}
<li class="usa-nav__secondary-item{{ ' is-current' if item.active }}">
<a class="usa-nav__link {{ ' usa-current' if item.active }}" href="{{ item.href }}" {% for attribute, value in
item.attributes %} {{attribute}}="{{value}}" {% endfor %}>
<span>{{ item.text }}</span>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
<!-- <section aria-label="Search component">
<form class="usa-search usa-search--small margin-bottom-2" role="search">
<label class="usa-sr-only" for="search-field">Search</label>
<input class="usa-input" id="search-field" type="search" name="search" />
<button class="usa-button" type="submit">
<img src="/static/images/usa-icons-bg/search--white.svg" class="usa-search__submit-icon" alt="Search" />
</button>
</form>
</section> -->
</div>
</div>
</nav>
</div>
</header>

View File

@@ -171,8 +171,8 @@
{% endif %}
<p class="status-hint margin-0 width-card ">
{{ notification.status|format_notification_status_as_time(
notification.created_at|format_datetime_short,
(notification.sent_at or notification.created_at)|format_datetime_short
notification.created_at|format_datetime_short_america,
(notification.sent_at or notification.created_at)|format_datetime_short_america
) }}
</p>
{% if displayed_on_single_line %}</span>{% endif %}

View File

@@ -11,10 +11,10 @@
<div class="grid-row">
{% if navigation_links %}
<div class="tablet:grid-col-2">
<div class="tablet:grid-col-2 margin-bottom-4">
{{ sub_navigation(navigation_links) }}
</div>
<div class="tablet:grid-col-10 padding-left-4 usa-prose site-prose">
<div class="tablet:grid-col-10 tablet:padding-left-4 usa-prose site-prose">
{% else %}
<div class="tablet:grid-col-10">
{% endif %}

View File

@@ -1,7 +1,7 @@
{% if help %}
{% include 'partials/tour.html' %}
{% else %}
<nav class="nav">
<nav class="nav margin-bottom-4">
<a class="usa-button margin-top-1 margin-bottom-5 width-full"
href="{{ url_for('.choose_template', service_id=current_service.id) }}">Send messages</a>
<ul class="usa-sidenav">

372
app/templates/new/base.html Normal file
View File

@@ -0,0 +1,372 @@
{% from "../components/banner.html" import banner %}
{% from "../components/components/skip-link/macro.njk" import usaSkipLink -%}
{% from "../components/components/header/macro.njk" import usaHeader -%}
{% from "../components/components/footer/macro.njk" import usaFooter -%}
<!DOCTYPE html>
<html lang="{{ htmlLang | default('en') }}" class="{{ htmlClasses }}">
<head>
<meta charset="utf-8" />
<title>
{% block pageTitle %}
{% block per_page_title %} {% endblock %}Notify.gov
<!-- on templates that were using content_template.html, we might need to use the {{ content_page_title }} variable for the per_page_title -->
{% endblock %}
</title>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="f0f0f0" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="1b1b1b" />
{% if config['NR_MONITOR_ON'] %}
{% include "partials/newrelic.html" -%}
{% endif %}
{# Ensure that older IE versions always render with the correct rendering engine #}
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
{% block headIcons %}
<link rel="shortcut icon" sizes="16x16 32x32 48x48" href="{{ asset_url('images/favicon.ico') }}" type="image/x-icon" />
<link rel="mask-icon" href="{{ asset_url('images/usa-mask-icon.svg') }}" color="{{ themeColor | default('#F0F0F0') }}">
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset_url('images/apple-touch-icon.png') }}">
<link rel="apple-touch-icon" href="{{ asset_url('images/apple-touch-icon.png') }}">
{% endblock %}
{% block head %}
<link rel="stylesheet" media="screen" href="{{ asset_url('css/styles.css') }}" />
{% block extra_stylesheets %}
{% endblock %}
{% if g.hide_from_search_engines %}
<meta name="robots" content="noindex" />
{% endif %}
<meta name="google-site-verification" content="niWnSqImOWz6mVQTYqNb5tFK8HaKSB4b3ED4Z9gtUQ0" />
{# The default og:image is added below head so that scrapers see any custom metatags first, and this is just a fallback #}
{% block meta_format_detection %}
<meta name="format-detection" content="telephone=no">
{% endblock %}
{% block meta %}
<meta property="og:site_name" content="Notify.gov">
<meta property="og:image" content="{{ asset_url('images/usa-opengraph-image.png') }}">
{% endblock %}
<script type="text/javascript" src="{{ asset_url('js/gtm_head.js') }}"></script>
{% endblock %}
</head>
<body class="usa-template__body {{ bodyClasses }}">
<script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>
{% block bodyStart %}
{% block extra_javascripts_before_body %}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-WX5NGWF"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{% endblock %}
{% endblock %}
{% block skipLink %}
{{ usaSkipLink({
"href": '#main-content',
"text": 'Skip to main content'
}) }}
{% endblock %}
<!-- \\#region header -->
{% block header %}
{% if current_user.is_authenticated %}
{% if current_user.platform_admin %}
{% set navigation = [
{
"href": url_for("main.show_accounts_or_dashboard"),
"text": "Current service",
"active": header_navigation.is_selected('accounts-or-dashboard')
},
{
"href": url_for('main.get_started'),
"text": "Using Notify",
"active": header_navigation.is_selected('using_notify')
},
{
"href": url_for('main.features'),
"text": "Features",
"active": header_navigation.is_selected('features')
},
{
"href": url_for('main.platform_admin_splash_page'),
"text": "Platform admin",
"active": header_navigation.is_selected('platform-admin')
},
{
"href": url_for('main.support'),
"text": "Contact us",
"active": header_navigation.is_selected('support')
}
] %}
{% if current_service %}
{% set secondaryNavigation = [
{
"href": url_for('main.service_settings', service_id=current_service.id),
"text": "Settings",
"active": secondary_navigation.is_selected('settings')
},
{
"href": url_for('main.sign_out'),
"text": "Sign out"
}
] %}
{% else %}
{% set secondaryNavigation = [
{
"href": url_for('main.sign_out'),
"text": "Sign out"
}
] %}
{% endif %}
{% else %}
{% set navigation = [
{
"href": url_for("main.show_accounts_or_dashboard"),
"text": "Current service",
"active": header_navigation.is_selected('accounts-or-dashboard')
},
{
"href": url_for('main.get_started'),
"text": "Using Notify",
"active": header_navigation.is_selected('using_notify')
},
{
"href": url_for('main.features'),
"text": "Features",
"active": header_navigation.is_selected('features')
},
{
"href": url_for('main.support'),
"text": "Contact us",
"active": header_navigation.is_selected('support')
},
{
"href": url_for('main.user_profile'),
"text": "User profile",
"active": header_navigation.is_selected('user-profile')
}
] %}
{% if current_service %}
{% set secondaryNavigation = [
{
"href": url_for('main.service_settings', service_id=current_service.id),
"text": "Settings",
"active": secondary_navigation.is_selected('settings')
},
{
"href": url_for('main.sign_out'),
"text": "Sign out"
}
] %}
{% else %}
{% set secondaryNavigation = [
{
"href": url_for('main.sign_out'),
"text": "Sign out"
}
] %}
{% endif %}
{% endif %}
{% else %}
<!-- Add navigation back after pilot -->
{# {% set navigation = [
{
"href": url_for('main.get_started'),
"text": "Using Notify",
"active": header_navigation.is_selected('using_notify')
},
{
"href": url_for('main.features'),
"text": "Features",
"active": header_navigation.is_selected('features')
},
{
"href": url_for('main.support'),
"text": "Contact us",
"active": header_navigation.is_selected('support')
},
{
"href": url_for('main.sign_in'),
"text": "Sign in",
"active": header_navigation.is_selected('sign-in')
}
] %} #}
{% endif %}
{{ usaHeader({
"homepageUrl": url_for('main.show_accounts_or_dashboard'),
"productName": "Notify",
"navigation": navigation,
"navigationClasses": "govuk-header__navigation--end",
"secondaryNavigation": secondaryNavigation,
"assetsPath": asset_path + "images"
}) }}
{% endblock %}
<!-- \\#endregion -->
<!-- \\#region block main -->
{% block main %}
<div class="grid-container">
{% block beforeContent %}
{% block backLink %}{% endblock %}
{% endblock %}
{% block mainClasses %}
<!-- notes set mainClasses = "margin-top-5 padding-bottom-5" where withoutnav_template was used and maybe templates that are using content_template -->
<main class="{{ mainClasses }}" id="main-content" role="main">
{% endblock %}
{% block content %}
{% block flash_messages %}
<!-- flash_message.html was from the withoutnav_template and is only included on child templates that was using withoutnav_template. Now, we can add in flash_message blocks and include 'flash_messages.html' to child templates that was using withoutnav_template. This will help to eliminate the use of a whole other parent template. -->
{% endblock %}
{% block maincolumn_content %}
{% block fromContentTemplatetwoColumnGrid %}
<div class="grid-row">
{% if navigation_links %}
<div class="tablet:grid-col-2">
{{ sub_navigation(navigation_links) }}
</div>
<div class="tablet:grid-col-10 padding-left-4 usa-prose site-prose">
{% else %}
<div class="tablet:grid-col-10">
{% endif %}
{% block content_column_content %}{% endblock %}
</div>
</div>
<!-- content_column_content block is from the content_template.html. We do not need this template. We can consolidate and move it to the base.html template. We can call on this block where child templates were using the content_template.html -->
{% endblock %}
{% endblock %}
{% endblock %}
</main>
</div>
{% endblock %}
<!-- \\#endregion -->
<!-- \\#region block footer -->
{% block footer %}
{% if current_service and current_service.research_mode %}
{% set meta_suffix = 'Built by the <a href="https://www.gsa.gov/about-us/organization/federal-acquisition-service/technology-transformation-services/tts-solutions" class="usa-link">Technology Transformation Services</a><span id="research-mode" class="research-mode">research mode</span>' %}
{% else %}
{% set meta_suffix = 'Built by the <a href="https://www.gsa.gov/about-us/organization/federal-acquisition-service/technology-transformation-services/tts-solutions" class="usa-link">Technology Transformation Services</a>' %}
{% endif %}
{{ usaFooter({
"classes": "js-footer",
"navigation": [
{
"title": "About Notify",
"columns": 1,
"items": [
{
"href": url_for("main.features"),
"text": "Features"
},
{
"href": url_for("main.roadmap"),
"text": "Roadmap"
},
{
"href": url_for("main.security"),
"text": "Security"
},
{
"href": url_for("main.terms"),
"text": "Terms of use"
},
]
},
{
"title": "Using Notify",
"columns": 1,
"items": [
{
"href": url_for("main.get_started"),
"text": "Get started"
},
{
"href": url_for("main.pricing"),
"text": "Pricing"
},
{
"href": url_for("main.trial_mode_new"),
"text": "Trial mode"
},
{
"href": url_for("main.message_status"),
"text": "Delivery status"
},
{
"href": url_for("main.guidance_index"),
"text": "Guidance"
},
{
"href": url_for("main.documentation"),
"text": "API documentation"
}
]
},
{
"title": "Support",
"columns": 1,
"items": [
{
"href": url_for('main.support'),
"text": "Contact us"
},
]
},
],
"meta": {
"items": meta_items,
"html": meta_suffix
}
}) }}
{% if current_user.is_authenticated %}
{% block sessionUserWarning %}
<dialog class="usa-modal" id="sessionTimer" aria-labelledby="sessionTimerHeading" aria-describedby="timerWarning">
<div class="usa-modal__content">
<div class="usa-modal__main">
<h2 class="usa-modal__heading" id="sessionTimerHeading">
Your session will end soon.
<span class="usa-sr-only">Please choose to extend your session or sign out. Your session will expire in 5 minutes or less.</span>
</h2>
<div class="usa-prose">
<p>You have been inactive for too long.
Your session will expire in <span id="timeLeft" role="timer"></span>.
</p>
</div>
<div class="usa-modal__footer">
<ul class="usa-button-group">
<li class="usa-button-group__item">
<button type="button" class="usa-button" id="extendSessionTimer" data-close-modal>
Extend Session
</button>
</li>
<li class="usa-button-group__item">
<button type="button" class="usa-button usa-button--unstyled padding-105 text-center" id="logOutTimer"
data-close-modal>
Sign out
</button>
</li>
</ul>
</div>
</div>
</div>
</dialog>
{% endblock %}
{% endif %}
{% endblock %}
<!-- \\#endregion -->
{% block bodyEnd %}
{% block extra_javascripts %}
{% endblock %}
<!--[if gt IE 8]><!-->
<script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}"></script>
<script type="text/javascript" src="{{ asset_url('js/uswds.min.js') }}"></script>
<!--<![endif]-->
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block per_page_title %}
{% block org_page_title %}{% endblock %} {{ current_org.name }}
{% endblock %}
{% block main %}
<div class="grid-container">
<div class="navigation-service usa-breadcrumb">
{% if current_user.platform_admin %}
<a href="{{ url_for('.organizations') }}" class="usa-link navigation-organization-link">Organizations</a>
{% endif %}
<div class="navigation-service">
{{ current_org.name }}
</div>
<a href="{{ url_for('main.choose_account') }}" class="usa-link navigation-service">Switch service</a>
</div>
<div class="grid-row">
<div class="grid-col-3">
{% include "org_nav.html" %}
</div>
<div class="grid-col-9">
{% block beforeContent %}
{% block backLink %}{% endblock %}
{% endblock %}
<main class="main" id="main-content" role="main" >
{% block content %}
{% include 'flash_messages.html' %}
{% block maincolumn_content %}{% endblock %}
{% endblock %}
</main>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block per_page_title %}
{% block service_page_title %}{% endblock %} {{ current_service.name }}
{% endblock %}
{% block main %}
<div class="grid-container">
{% block serviceNavigation %}
{% include "service_navigation.html" %}
{% endblock %}
<!-- The withnav_template can be used to replace the settings_template. when it comes to setting_template and withnav_template, this service_navigation.html is only used in withnav_template and in settings_template, it was not used. That is one out of the two differences between settings template and withnav template. Child templates that extends settings_template, include the block serviceNavigation but leave it empty. Within the app, the only pages settings_template.html is used: manage-users.html, service-settings.html, and user-profile.html -->
<div class="grid-row margin-top-5">
{% if help %}
<div class="grid-col-3">
{% else %}
<div class="grid-col-3">
{% endif %}
{% block sideNavigation %}
{% include "main_nav.html" %}
<!-- include settings_nav.html for child templates that used settings_template -->
{% endblock %}
</div>
{% if help %}
<div class="grid-col-8">
{% else %}
<div class="grid-col-9 padding-left-4">
{% endif %}
{% block beforeContent %}
{% block backLink %}{% endblock %}
{% endblock %}
<main id="main-content" role="main" class="usa-prose site-prose margin-bottom-10">
{% block content %}
{% include 'flash_messages.html' %}
{% block maincolumn_content %}{% endblock %}
{% endblock %}
</main>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,37 @@
# New Templates Glossary
This document serves as a glossary for the templates directory structure of the project.
## Directory Structure
- `/templates`
- `base.html`: The main base template from which all other templates inherit. This template is a combination of `main_template`, `admin_template`, `withoutnav_template` and `content_template`.
- **/layouts**: Contains base templates and shared layouts used across the site. Simply put, it defines the overall structure or skeleton of the application (less frequently revised).
- `withnav_template.html`: A variation of the base layout that includes a sidebar.
- `org_template.html`: A variaton of the withnav_template
- **/components**: Houses reusable UI components that can be included in multiple templates and can be tailored with different content or links depending on the context.(more frequently revised or customized)
- `header.html`: Template for the site's header, included in `base.html`.
- `footer.html`: Template for the site's footer, included in `base.html`.
- **/views** (or **/pages**): Individual page templates that use the base layouts, components, and partials to present content.
### Best Practices
- Use **inheritance** (`{% extends %}`) to build on base layouts.
- Employ **components** (`{% include %}`) for reusable UI elements to keep the code DRY and facilitate easier updates.
### Observation Notes
- The macro-options.json files in the header and footer component act as structural guides. They aren't directly used as data passed to the usaFooter function/macro. Instead, these files outline the expected properties and provide a description of their purpose. The `usaFooter` macro component is currently only invoked in the `admin_template`, which will eventually serve as the `base.html` template. This will simplify the approach when we change the footer macros to componenets by eliminating the need to dynamically pass this data from the base.html template.
### Old Layout Templates We Don't Need
- withoutnav_template.html Delete
- main_template.html Delete
- settings_templates.html `withnav_template` can be used to replace `settings_template`.
- settings_nav.html (move to /new/navigation directory)
- main_nav.html (move to /new/navigation directory)
- service_navigation.html (move to /new/navigation directory)
- org_template, could be under it's own directory called /layout/organization
- org_nav.html (move to /new/navigation directory)
- content_template.html Delete

View File

@@ -2,12 +2,12 @@
<p class='bottom-gutter'>
{% if job.scheduled_for %}
{% if job.processing_started %}
Sent by {{ job.created_by.name }} on {{ job.processing_started|format_datetime_short }}
Sent by {{ job.created_by.name }} on {{ job.processing_started|format_datetime_short_america }}
{% else %}
Uploaded by {{ job.created_by.name }} on {{ job.created_at|format_datetime_short }}
Uploaded by {{ job.created_by.name }} on {{ job.created_at|format_datetime_short_america }}
{% endif %}
{% else %}
Sent by {{ job.created_by.name }} on {{ job.created_at|format_datetime_short }}
Sent by {{ job.created_by.name }} on {{ job.created_at|format_datetime_short_america }}
{% endif %}
</p>
{% if job.status == 'sending limits exceeded'%}

View File

@@ -1,7 +1,7 @@
{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading, notification_status_field %}
{% from "components/page-footer.html" import page_footer %}
<div class="ajax-block-container" aria-labelledby='pill-selected-item'>
<div class="ajax-block-container table-wrapper" aria-labelledby='pill-selected-item'>
<div class="dashboard-table bottom-gutter-3-2">
{% call(item, row_number) list_table(

View File

@@ -8,16 +8,16 @@
<div class="grid-container">
<div class="grid-row margin-top-5">
{% if help %}
<div class="grid-col-3">
<div class="tablet:grid-col-3">
{% else %}
<div class="grid-col-3">
<div class="tablet:grid-col-3 margin-bottom-4">
{% endif %}
{% include "settings_nav.html" %}
</div>
{% if help %}
<div class="grid-col-8">
<div class="tablet:grid-col-8">
{% else %}
<div class="grid-col-9 padding-left-4">
<div class="tablet:grid-col-9 tablet:padding-left-4">
{% endif %}
{% block beforeContent %}
{% block backLink %}{% endblock %}

View File

@@ -5,6 +5,7 @@
<div class="ajax-block-container" id='pill-selected-item'>
{% if notifications %}
<div class="table-wrapper">
<div class='dashboard-table'>
{% endif %}
{% call(item, row_number) list_table(
@@ -29,6 +30,7 @@
{% endcall %}
{% if notifications %}
</div>
</div>
{% endif %}
{% if show_pagination %}

View File

@@ -14,24 +14,26 @@
{% block maincolumn_content %}
{{ page_header('Callbacks') }}
<div class="bottom-gutter-3-2 dashboard-table body-copy-table">
{% call mapping_table(
caption='General',
field_headings=['Label', 'Value', 'Action'],
field_headings_visible=False,
caption_visible=False
) %}
{% call row() %}
{{ text_field('Delivery receipts') }}
{{ optional_text_field(delivery_status_callback, truncate=true) }}
{{ edit_field('Change', url_for('.delivery_status_callback', service_id=current_service.id)) }}
{% endcall %}
<div class="table-wrapper">
<div class="bottom-gutter-3-2 dashboard-table body-copy-table">
{% call mapping_table(
caption='General',
field_headings=['Label', 'Value', 'Action'],
field_headings_visible=False,
caption_visible=False
) %}
{% call row() %}
{{ text_field('Delivery receipts') }}
{{ optional_text_field(delivery_status_callback, truncate=true) }}
{{ edit_field('Change', url_for('.delivery_status_callback', service_id=current_service.id)) }}
{% endcall %}
{% call row() %}
{{ text_field('Received text messages') }}
{{ optional_text_field(received_text_messages_callback, truncate=true) }}
{{ edit_field('Change', url_for('.received_text_messages_callback', service_id=current_service.id)) }}
{% endcall %}
{% endcall %}
{% call row() %}
{{ text_field('Received text messages') }}
{{ optional_text_field(received_text_messages_callback, truncate=true) }}
{{ edit_field('Change', url_for('.received_text_messages_callback', service_id=current_service.id)) }}
{% endcall %}
{% endcall %}
</div>
</div>
{% endblock %}

View File

@@ -1,6 +1,5 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/table.html" import list_table, field, text_field, index_field, hidden_field_heading %}
{% from "components/page-header.html" import page_header %}
{% from "components/components/button/macro.njk" import usaButton %}
{% from "components/components/skip-link/macro.njk" import usaSkipLink %}
@@ -9,7 +8,7 @@
{% set file_contents_header_id = 'file-preview' %}
{% block service_page_title %}
{{ "Preview of {}".format(template.name) }}
{{ "Select delivery time" }}
{% endblock %}
@@ -19,11 +18,11 @@
{% block maincolumn_content %}
{{ page_header('Preview of {}'.format(template.name)) }}
{{ page_header('Select delivery time') }}
{{ template|string }}
<div class="bottom-gutter-3-2">
<form method="post" enctype="multipart/form-data" action="{{url_for('main.start_job', service_id=current_service.id, upload_id=upload_id)}}" class='page-footer'>
<form method="post" enctype="multipart/form-data" action="{{url_for('main.preview_job', service_id=current_service.id, template_id=template_id, upload_id=upload_id)}}" class='page-footer'>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
{% if choose_time_form %}
{{ choose_time_form.scheduled_for(param_extensions={
@@ -37,55 +36,11 @@
{% endif %}
{% set button_text %}
Send {{ count_of_recipients|message_count(template.template_type) }}
Preview
{% endset %}
{{ usaButton({ "text": button_text }) }}
</form>
</div>
{% if not request.args.from_test %}
<h2 class="font-body-lg" id="{{ file_contents_header_id }}">{{ original_file_name }}</h2>
<div class="fullscreen-content" data-module="fullscreen-table">
{% call(item, row_number) list_table(
recipients.displayed_rows,
caption=original_file_name,
caption_visible=False,
field_headings=[
'<span class="usa-sr-only">Row in file</span><span aria-hidden="true">1</span>'|safe
] + recipients.column_headers
) %}
{% call index_field() %}
<span>
{% if (item.index + 2) == preview_row %}
{{ item.index + 2 }}
{% else %}
<a class="usa-link" href="{{ url_for('.check_messages', service_id=current_service.id, template_id=template.id, upload_id=upload_id, row_index=(item.index + 2), original_file_name=original_file_name) }}">{{ item.index + 2 }}</a>
{% endif %}
</span>
{% endcall %}
{% for column in recipients.column_headers %}
{% if item[column].ignore %}
{{ text_field(item[column].data or '', status='default') }}
{% else %}
{{ text_field(item[column].data or '') }}
{% endif %}
{% endfor %}
{% if item[None].data %}
{% for column in item[None].data %}
{{ text_field(column, status='default') }}
{% endfor %}
{% endif %}
{% endcall %}
</div>
{% endif %}
{% if count_of_displayed_recipients < count_of_recipients %}
<p class="table-show-more-link">
Only showing the first {{ count_of_displayed_recipients }} rows
</p>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,77 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/table.html" import list_table, field, text_field, hidden_field_heading %}
{% from "components/page-header.html" import page_header %}
{% from "components/components/button/macro.njk" import usaButton %}
{% from "components/components/skip-link/macro.njk" import usaSkipLink %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% set file_contents_header_id = 'file-preview' %}
{% block service_page_title %}
{{ "Preview of {}".format(template.name) }}
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": back_link_from_preview }) }}
{% endblock %}
{% block maincolumn_content %}
{{ page_header('Preview') }}
<div>
<p class="sms-message-scheduler">Scheduled: {{ scheduled_for if scheduled_for else 'Now'}}</p>
<p class="sms-message-file-name">File: {{original_file_name}}</p>
<p class="sms-message-template">Template: {{template.name}}</p>
<p class="sms-message-sender" >From: {{ template.sender }}</p>
</div>
<h2 id="{{ file_contents_header_id }}">Message</h2>
<div class="preview-message"> {{ simplifed_template|string }}</div>
{% if not request.args.from_test %}
<h2>Recipients list</h2>
<div>
<ul class="usa-icon-list">
<li class="usa-icon-list__item">
<img src="{{ url_for('static', filename='img/material-icons/description.svg') }}" alt="Description Icon">
<div class="usa-icon-list__content">
<h3>{{ original_file_name }}</h3>
</div>
</li>
</ul>
</div>
<div class="usa-table-container--scrollable" tabindex="0">
{% call(item, row_number) list_table(
recipients.displayed_rows,
caption="Note: Only the first 5 rows are displayed here.",
caption_visible=True,
field_headings=recipients.column_headers
) %}
{% for column in recipients.column_headers %}
{% if item[column].ignore %}
{{ text_field(item[column].data or '', status='default') }}
{% else %}
{{ text_field(item[column].data or '') }}
{% endif %}
{% endfor %}
{% if item[None].data %}
{% for column in item[None].data %}
{{ text_field(column, status='default') }}
{% endfor %}
{% endif %}
{% endcall %}
</div>
{% endif %}
<!-- <div class="bottom-gutter-3-2">
<p>This is a placeholder: This message will be delivered to <b>400 phone numbers</b> and will use a total of <b>800 message parts</b>, leaving Washington DSHS with <b>249,200 message parts remaining</b>.</p>
</div> -->
<form method="post" enctype="multipart/form-data" action="{{url_for('main.start_job', service_id=current_service.id, upload_id=upload_id)}}" class='page-footer'>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<h3>Does everything look good?</h3>
{% set button_text %}
{{ "Schedule" if scheduled_for else 'Send'}}
{% endset %}
{{ usaButton({ "text": button_text }) }}
</form>
{% endblock %}

View File

@@ -1,115 +1,117 @@
{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading %}
<div class='dashboard-table ajax-block-container'>
{% call(item, row_number) list_table(
jobs,
caption="Recent files uploaded",
caption_visible=False,
empty_message=(
'You have not uploaded any files yet.'
),
field_headings=[
'File',
'Status'
],
field_headings_visible=False
) %}
{% call row_heading() %}
<div class="file-list">
<a class="file-list-filename-large usa-link" href="{{ url_for('.view_job', service_id=current_service.id, job_id=item.id) }}">{{ item.original_file_name }}</a>
{% if item.scheduled %}
<span class="file-list-hint-large">
Sending {{
item.scheduled_for|format_datetime_relative
}}
</span>
{% else %}
<span class="file-list-hint-large">
Sent {{
(item.scheduled_for or item.created_at)|format_datetime_relative
}}
</span>
{% endif %}
</div>
{% endcall %}
{% call field() %}
{% if item.scheduled %}
{% if link %}
<a class="usa-link display-flex" href="{{ link }}">
{% endif %}
<span class="big-number-smallest">
<span class="big-number-number">
{% if item.notification_count is number %}
{% if currency %}
{{ "{}{:,.2f}".format(currency, item.notification_count) }}
{% else %}
{{ "{:,}".format(item.notification_count) }}
{% endif %}
{% else %}
{{ item.notification_count }}
{% endif %}
</span>
{% if item.notification_count %}
<span class="big-number-label">{{ item.notification_count|message_count_label(item.template_type,suffix='waiting to send') }}</span>
{% endif %}
<div class="table-wrapper">
<div class='dashboard-table ajax-block-container'>
{% call(item, row_number) list_table(
jobs,
caption="Recent files uploaded",
caption_visible=False,
empty_message=(
'You have not uploaded any files yet.'
),
field_headings=[
'File',
'Status'
],
field_headings_visible=False
) %}
{% call row_heading() %}
<div class="file-list">
<a class="file-list-filename-large usa-link" href="{{ url_for('.view_job', service_id=current_service.id, job_id=item.id) }}">{{ item.original_file_name }}</a>
{% if item.scheduled %}
<span class="file-list-hint-large">
Sending {{
item.scheduled_for|format_datetime_relative
}}
</span>
{% if link %}
</a>
{% endif %}
{% else %}
<div class="grid-row">
<div class="grid-col-4">
{% if link %}
<a class="usa-link display-flex" href="{{ link }}">
{% endif %}
{% else %}
<span class="file-list-hint-large">
Sent {{
(item.scheduled_for or item.created_at)|format_datetime_relative
}}
</span>
{% endif %}
</div>
{% endcall %}
{% call field() %}
{% if item.scheduled %}
{% if link %}
<a class="usa-link display-flex" href="{{ link }}">
{% endif %}
<span class="big-number-smallest">
<span class="big-number-number">
{{ "{:,}".format(item.notifications_sending) }}
</span>
<span class="big-number-label">pending</span>
</span>
{% if link %}
</a>
{% endif %}
</div>
<div class="grid-col-4">
<span class="big-number-smallest">
<span class="big-number-number">
{% if item.notifications_delivered is number %}
{{ "{:,}".format(item.notifications_delivered) }}
{% else %}
{{ item.notifications_delivered }}
{% endif %}
</span>
<span class="big-number-label">delivered</span>
</span>
</div>
<div class="grid-col-4">
{% if link %}
<a class="usa-link display-flex" href="{{ link }}">
{% endif %}
<span class="big-number-smallest">
<span class="big-number-number">
{% if item.notifications_failed is number %}
{% if currency %}
{{ "{}{:,.2f}".format(currency, item.notifications_failed) }}
{% if item.notification_count is number %}
{% if currency %}
{{ "{}{:,.2f}".format(currency, item.notification_count) }}
{% else %}
{{ "{:,}".format(item.notification_count) }}
{% endif %}
{% else %}
{{ "{:,}".format(item.notifications_failed) }}
{{ item.notification_count }}
{% endif %}
{% else %}
{{ item.notifications_failed }}
</span>
{% if item.notification_count %}
<span class="big-number-label">{{ item.notification_count|message_count_label(item.template_type,suffix='waiting to send') }}</span>
{% endif %}
</span>
<span class="big-number-label">failed</span>
{% if link %}
</a>
{% endif %}
{% else %}
<div class="grid-row">
<div class="grid-col-4">
{% if link %}
<a class="usa-link display-flex" href="{{ link }}">
{% endif %}
<span class="big-number-smallest">
<span class="big-number-number">
{{ "{:,}".format(item.notifications_sending) }}
</span>
<span class="big-number-label">pending</span>
</span>
{% if link %}
</a>
{% endif %}
</div>
<div class="grid-col-4">
<span class="big-number-smallest">
<span class="big-number-number">
{% if item.notifications_delivered is number %}
{{ "{:,}".format(item.notifications_delivered) }}
{% else %}
{{ item.notifications_delivered }}
{% endif %}
</span>
<span class="big-number-label">delivered</span>
</span>
{% if link %}
</a>
</div>
<div class="grid-col-4">
{% if link %}
<a class="usa-link display-flex" href="{{ link }}">
{% endif %}
<span class="big-number-smallest">
<span class="big-number-number">
{% if item.notifications_failed is number %}
{% if currency %}
{{ "{}{:,.2f}".format(currency, item.notifications_failed) }}
{% else %}
{{ "{:,}".format(item.notifications_failed) }}
{% endif %}
{% else %}
{{ item.notifications_failed }}
{% endif %}
</span>
<span class="big-number-label">failed</span>
</span>
{% if link %}
</a>
{% endif %}
</div></div>
{% endif %}
</div></div>
{% endif %}
{% endcall %}
{% endcall %}
{% endcall %}
</div>
</div>

View File

@@ -29,7 +29,7 @@
{{ ajax_block(partials, updates_url, 'template-statistics') }}
<h2 class="margin-top-4 margin-bottom-1">Recent Batches</h2>
<div>
<div class="table-wrapper">
<table class="usa-table usa-table--borderless job-table">
<thead class="table-field-headings">
<tr>

View File

@@ -41,7 +41,7 @@
<h3>To create and format your message</h3>
<ol class="list">
<li>All messages start from a template</li>
<li>Click “Send Messages”. Youll see existing templates.</li>
<li>Click “<a href={{ url_for('.choose_template', service_id=current_service.id) }}>Send Messages</a>”. Youll see existing templates.</li>
<li>Add a new template or choose an existing template and select Edit.</li>
</ol>
@@ -120,7 +120,7 @@
{# Identify your program #}
<h2 class="padding-top-1" id="identify-program">Identify your program</h2>
<h2 class="padding-top-1" id="indentify-program">Identify your program</h2>
<p>You can help your recipients identify your texts as legitimate by customizing your messages to clearly state who they
are from. Consider using the program or benefit name that is most familiar to your recipients.</p>

View File

@@ -10,8 +10,8 @@
<h1 class="font-body-2xl margin-bottom-3">Delivery status</h1>
<p>Notifys real-time dashboard lets you check the status of any message.</p>
<p>For <a class="usa-link" href="{{ url_for("main.security") }}">security</a>, this information is only available for seven days after a message has been sent. You can download a report, including a list of sent messages, for your own records.</p>
<p>This page describes the statuses youll see when youre signed in to Notify.</p>
<p>For <a class="usa-link" href="{{ url_for('main.security') }}">security</a>, this information is only available for seven days after a message has been sent. You can download a report, including a list of sent messages, for your own records.</p>
<p>This page describes the statuses you'll see when you're signed in to Notify.</p>
<!-- <p>If youre using the Notify API, read our <a class="usa-link" href="{{ url_for('.documentation') }}">documentation</a> for a list of API statuses.<p>
@@ -53,13 +53,13 @@
caption_visible=False
) %}
{% for message_status, description in [
('Total', 'The total number of messages that have been sent during the last seven days.'),
('Pending', 'Notify has sent the message to the provider. The provider will try to deliver the message to the recipient for up to 72 hours. Notify is waiting for delivery information.'),
('Sent', 'The mobile networks may not provide any more delivery information.'),
('Delivered', 'The message was successfully delivered. Notify cannot tell you if a user has opened or read a message.'),
('Not delivered', ('The provider could not deliver the message. This can happen if the phone number was wrong or if the network operator rejects the message. If youre sure that these phone numbers are correct, you should <a class="usa-link" href="' + url_for(".support") + '">contact us</a>. If not, you should remove them from your database. Youll still be charged for text messages that cannot be delivered.')|safe),
('Phone not accepting messages right now', 'The provider could not deliver the message. This can happen when the recipients phone is off, has no signal, or their text message inbox is full. You can try to send the message again. Youll still be charged for text messages to phones that are not accepting messages.'),
('Technical failure', 'Your message was not sent because there was a problem between Notify and the provider. Youll have to try sending your messages again. You will not be charged for text messages that are affected by a technical failure.'),
] %}
('Failed', 'The provider could not deliver the message. This can happen if the phone number was wrong or if the network operator
rejects the message. If youre sure that these phone numbers are correct, you should <a class="usa-link" href="/support">contact us</a>. If not, you should remove them from your database. Youll still be charged for text messages that
cannot be delivered.' | safe),
] %}
{% call row() %}
{{ text_field(message_status) }}
{{ text_field(description) }}

View File

@@ -5,7 +5,7 @@
{% from "components/components/button/macro.njk" import usaButton %}
{% block service_page_title %}
{{ "Error" if error else "Preview of {}".format(template.name) }}
{{ "Error" if error else "Select delivery time" }}
{% endblock %}
{% block backLink %}
@@ -40,17 +40,16 @@
{% endcall %}
</div>
{% else %}
{{ page_header('Preview of {}'.format(template.name)) }}
{{ page_header('Select delivery time') }}
{% endif %}
{{ template|string }}
<div class="js-stick-at-bottom-when-scrolling">
<form method="post" enctype="multipart/form-data" action="{{url_for(
'main.send_notification',
'main.preview_notification',
service_id=current_service.id,
template_id=template.id,
help='3' if help else 0
template_id=template.id
)}}" class='page-footer'>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
{% if not error %}
@@ -64,7 +63,9 @@
}
}) }}
{% endif %}
{% set button_text %}Send 1 {{ 1|message_count_label(template.template_type, suffix='') }}{% endset %}
{% set button_text %}
Preview
{% endset %}
{{ usaButton({ "text": button_text }) }}
{% endif %}
</form>

View File

@@ -0,0 +1,74 @@
{% extends "withnav_template.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% from "components/page-header.html" import page_header %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% from "components/components/button/macro.njk" import usaButton %}
{% block service_page_title %}
{{ "Error" if error else "Preview" }}
{% endblock %}
{% block backLink %}
{{ usaBackLink({ "href": back_link_from_preview }) }}
{% endblock %}
{% block maincolumn_content %}
{% if error == 'not-allowed-to-send-to' %}
<div class="bottom-gutter">
{% call banner_wrapper(type='dangerous') %}
{% with
count_of_recipients=1,
template_type_label=(
'phone number' if template.template_type == 'sms' else 'email address'
)
%}
{% include "partials/check/not-allowed-to-send-to.html" %}
{% endwith %}
{% endcall %}
</div>
{% elif error == 'too-many-messages' %}
<div class="bottom-gutter">
{% call banner_wrapper(type='dangerous') %}
{% include "partials/check/too-many-messages.html" %}
{% endcall %}
</div>
{% elif error == 'message-too-long' %}
{# the only row_errors we can get when sending one off messages is that the message is too long #}
<div class="bottom-gutter">
{% call banner_wrapper(type='dangerous') %}
{% include "partials/check/message-too-long.html" %}
{% endcall %}
</div>
{% else %}
{{ page_header('Preview') }}
{% endif %}
<div>
<p class="sms-message-scheduler">Scheduled: {{ scheduled_for if scheduled_for else 'Now'}}</p>
<p class="sms-message-template">Template: {{template.name}}</p>
<p class="sms-message-sender" >From: {{ template.sender }}</p>
<p class="sms-message-sender" >To: {{ recipient }}</p>
</div>
<h2 id="{{ file_contents_header_id }}">Message</h2>
<div class="preview-message"> {{ simplifed_template|string }}</div>
<div class="js-stick-at-bottom-when-scrolling">
<form method="post" enctype="multipart/form-data" action="{{url_for(
'main.send_notification',
service_id=current_service.id,
template_id=template.id,
help='3' if help else 0
)}}" class='page-footer'>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<!-- <p>Placeholder: This message will be delivered to <b>400 phone numbers</b> and will use a total of <b>800 message parts</b>, leaving Washington DSHS with <b>249,200 message parts remaining</b>.</p> -->
<h3>Does everything look good?</h3>
{% if not error %}
{% set button_text %}
{{ "Schedule" if scheduled_for else 'Send'}}
{% endset %}
{{ usaButton({ "text": button_text }) }}
{% endif %}
</form>
</div>
{% endblock %}

View File

@@ -44,31 +44,33 @@
</div>
</div>
<div class="dashboard-table">
{% call(item, row_number) list_table(
notifications_by_type|reverse,
caption='Messages sent since May 2023',
caption_visible=False,
field_headings=[
'Date',
99|message_count_noun('email')|capitalize,
99|message_count_noun('sms')|capitalize,
],
empty_message='No data to show'
) %}
{% call field() %}
{{ item.date | format_date_normal }}
<div class="table-wrapper">
<div class="dashboard-table">
{% call(item, row_number) list_table(
notifications_by_type|reverse,
caption='Messages sent since May 2023',
caption_visible=False,
field_headings=[
'Date',
99|message_count_noun('email')|capitalize,
99|message_count_noun('sms')|capitalize,
],
empty_message='No data to show'
) %}
{% call field() %}
{{ item.date | format_date_normal }}
{% endcall %}
{% call field() %}
{{ item.emails|format_thousands }}
{% endcall %}
{% call field() %}
{{ item.sms|format_thousands }}
{% endcall %}
{% endcall %}
{% call field() %}
{{ item.emails|format_thousands }}
{% endcall %}
{% call field() %}
{{ item.sms|format_thousands }}
{% endcall %}
{% endcall %}
<p class="table-show-more-link">
Only showing the last {{ notifications_by_type|length }} days
</p>
<p class="table-show-more-link">
Only showing the last {{ notifications_by_type|length }} days
</p>
</div>
</div>
<h2 class="govuk-heading-m">
@@ -82,26 +84,28 @@
) }}
</div>
</div>
<div class="dashboard-table">
{% call(item, row_number) list_table(
processing_time | reverse,
caption='Messages sent within 10 seconds',
caption_visible=False,
field_headings=[
'Date', 'Percentage'
],
empty_message='No data to show'
) %}
{% call field() %}
{{ item.date | format_date_normal }}
<div class="table-wrapper">
<div class="dashboard-table">
{% call(item, row_number) list_table(
processing_time | reverse,
caption='Messages sent within 10 seconds',
caption_visible=False,
field_headings=[
'Date', 'Percentage'
],
empty_message='No data to show'
) %}
{% call field() %}
{{ item.date | format_date_normal }}
{% endcall %}
{% call field() %}
{{ '{:.2f}%'.format(item.percentage_under_10_seconds) }}
{% endcall %}
{% endcall %}
{% call field() %}
{{ '{:.2f}%'.format(item.percentage_under_10_seconds) }}
{% endcall %}
{% endcall %}
<p class="table-show-more-link">
Only showing the last {{ processing_time|length }} days
</p>
<p class="table-show-more-link">
Only showing the last {{ processing_time|length }} days
</p>
</div>
</div>
<h2 class="govuk-heading-m">
@@ -120,23 +124,25 @@
<span class="usa-sr-only">using Notify.</span>
</div>
</div>
<div class="dashboard-table">
{% call(item, row_number) list_table(
organizations_using_notify,
caption='Organizations using Notify',
caption_visible=False,
field_headings=[
'Organization', 'Number of live services'
],
empty_message='No data to show'
) %}
{% call field() %}
{{ item.organization_name }}
<div class="table-wrapper">
<div class="dashboard-table">
{% call(item, row_number) list_table(
organizations_using_notify,
caption='Organizations using Notify',
caption_visible=False,
field_headings=[
'Organization', 'Number of live services'
],
empty_message='No data to show'
) %}
{% call field() %}
{{ item.organization_name }}
{% endcall %}
{% call field() %}
{{ item.count_of_live_services }}
{% endcall %}
{% endcall %}
{% call field() %}
{{ item.count_of_live_services }}
{% endcall %}
{% endcall %}
</div>
</div>
{% endblock %}

View File

@@ -30,7 +30,7 @@ more parts towards the allowance if you:</p>
</ul>
<h3 class="font-body-lg" id="long-text-messages">Long text messages</h3>
<p>If a text message is longer than 160 characters (including spaces), it counts as more than one message.</p>
<p>If a text message is longer than 160 characters (including spaces), it counts as more than one message part.</p>
<div class="bottom-gutter-3-2">
{% call mapping_table(

View File

@@ -50,9 +50,9 @@
<li>Message send/failure analytics</li>
</ul>
<h3id="next">Next</h3>
<h3 id="next">Next</h3>
<p>If the pilot is successful, we hope to recruit additional high-impact partners to improve outcomes for low-income individuals and families.</p>
<p>If the pilot is successful, we hope to recruit additional partners to improve outcomes for low-income individuals and families.</p>
<p>Goals during this stage:</p>

View File

@@ -65,9 +65,9 @@
<h3 class="font-body-lg">Protect sensitive information</h3>
<p>Some messages include sensitive information like security codes or password reset links.</p>
<p>If youre sending a message with sensitive information, you can choose to hide those details on the Notify dashboard once the message has been sent. This means that only the message recipient will be able to see that information.</p>
<img src="{{ asset_url('images/product/security-review-message.png') }}"
alt="Screenshot of a test message in review with the link to 'hide personalization after sending' circled.">
<h2 class="font-body-lg" id="user-permissions-signing-in">User permissions and signing in</h2>
<p>You can set different user permissions in Notify. This lets you control who in your team has access to certain parts of the service.</p>
<h3 class="font-body-lg">Two-factor authentication</h3>
<p>To sign in to Notify, youll need to enter:</p>
<ul class="list list-bullet">
@@ -76,11 +76,6 @@
</ul>
<p>If signing in with a text message is a problem for your team, <a class="usa-link" href="{{ url_for('main.support') }}">contact us</a> to find out about using an email link instead.</p>
<img src="{{ asset_url('images/product/security-review-message.png') }}"
alt="Screenshot of a teat message in review with the link to 'hide personalization after sending' circled.">
<h4>How to hide PII after sending a message</h4>
<h3>User permissions and signing in</h3>
<p>You can set different user permissions in Notify. This lets you control who in your team has access to certain parts of
the service.</p>
@@ -93,32 +88,4 @@
</ul>
<p>If signing in with a text message is a problem for your team, <a href="https://beta.notify.gov/support">contact us</a> to find out about using an email link instead.</p>
<!-- <h2 class="font-body-lg" id="information-risk-management">Information risk management</h2>
<p>Our approach to information risk management follows NCSC guidance. It assesses:</p>
<ul class="list list-bullet">
<li>how Notify is built</li>
<li>the infrastructure Notify is built upon</li>
<li>support for the Notify service</li>
</ul>
<p>This approach also applies to the service providers Notify uses to send messages.</p> -->
<!-- <h2 class="font-body-lg" id="how-we-manage-risk">How we manage risks on Notify</h2>
<p>Things we do to manage risks on Notify include:</p>
<ul class="list list-bullet">
<li>formal risk assessments based on <a class="usa-link" href="http://www.iso.org/iso/catalogue_detail?csnumber=56742">ISO 27005:2011</a> and National Cyber Security Centre guidance</li>
<li><a class="usa-link" href="https://www.ncsc.gov.uk/information/check-penetration-testing">CHECK</a>-based testing, both annually and when any major changes are made to Notify</li>
<li>residual risk statement preparation and active management of the risk treatment plan</li>
<li>regular updates to the Privacy Impact Assessment</li>
<li>security impact assessments</li>
</ul> -->
<!-- <h2 class="font-body-lg" id="cabinet-office-approval">Cabinet Office approval</h2>
<p>Notify has been assessed and approved by the Cabinet Office Senior Information Risk Officer (SIRO). The SIRO checks this approval once a year.</p>
<p>Notify also has approval from the Office of the Governments SIRO to host data within the EEA.</p>
<h2 class="font-body-lg" id="classifications-and-security-vetting">Classifications and security vetting</h2>
<p>You can use Notify to send messages classified as OFFICIAL or OFFICIAL-SENSITIVE under the <a class="usa-link" href="https://www.gov.uk/government/publications/government-security-classifications">Government Security Classifications</a> policy.</p>
<p>Notify does not process data classified as SECRET or TOP SECRET.</p>
<p>The Notify team has Security Check (SC) level clearance from <a class="usa-link" href="https://www.gov.uk/government/organizations/united-kingdom-security-vetting">United Kingdom Security Vetting</a> (UKSV).</p> -->
{% endblock %}

View File

@@ -13,9 +13,9 @@
<h1 class="font-body-2xl margin-bottom-3">Contact us</h1>
<p>Notify is designed to be easy to use.</p>
<ul class="list list-bullet">
<li>For information on personalization and data preparation, see <a href="/using-notify/guidance">Guidance</a>.</li>
<li>For help interpreting delivery reports, see <a href="/using-notify/delivery-status">Delivery Status</a>.</li>
<li>For details on pricing and what counts as a message part, see <a href="/using-notify/pricing"></a>Pricing.</li>
<li>For information on personalization and data preparation, see <a href={{ url_for("main.guidance_index") }}>Guidance</a>.</li>
<li>For help interpreting delivery reports, see <a href={{ url_for("main.message_status") }}>Delivery Status</a>.</li>
<li>For details on pricing and what counts as a message part, see <a href={{ url_for("main.pricing") }}>Pricing</a>.</li>
</ul>
<p>If you have other questions, we are available at <a class="usa-link" href="mailto:notify-support@gsa.gov">notify-support@gsa.gov</a>.</p>

View File

@@ -73,6 +73,7 @@
</div> #}
{% if months %}
<div class="table-wrapper">
<div class="dashboard-table usage-table body-copy-table margin-top-4">
{% call(item, row_index) list_table(
months,
@@ -129,6 +130,7 @@
{% endcall %}
</div>
</div>
{% endif %}
</div>

View File

@@ -9,16 +9,16 @@
{% include "service_navigation.html" %}
<div class="grid-row margin-top-5">
{% if help %}
<div class="grid-col-3">
<div class="tablet:grid-col-3">
{% else %}
<div class="grid-col-3">
<div class="tablet:grid-col-3">
{% endif %}
{% include "main_nav.html" %}
</div>
{% if help %}
<div class="grid-col-8">
{% else %}
<div class="grid-col-9 padding-left-4">
<div class="tablet:grid-col-9 tablet:padding-left-4">
{% endif %}
{% block beforeContent %}
{% block backLink %}{% endblock %}