Merge pull request #2256 from GSA/feature/sidenav-expand-sublinks

Updated code to allow sidenav expand on click
This commit is contained in:
Jonathan Bobel
2025-01-14 14:08:38 -05:00
committed by GitHub
59 changed files with 408 additions and 834 deletions

View File

@@ -161,7 +161,7 @@
"filename": "app/config.py",
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
"is_verified": false,
"line_number": 123,
"line_number": 120,
"is_secret": false
}
],
@@ -555,7 +555,7 @@
"filename": "tests/app/main/views/test_register.py",
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
"is_verified": false,
"line_number": 201,
"line_number": 200,
"is_secret": false
},
{
@@ -563,7 +563,7 @@
"filename": "tests/app/main/views/test_register.py",
"hashed_secret": "bb5b7caa27d005d38039e3797c3ddb9bcd22c3c8",
"is_verified": false,
"line_number": 274,
"line_number": 273,
"is_secret": false
}
],
@@ -684,5 +684,5 @@
}
]
},
"generated_at": "2024-11-21T23:08:45Z"
"generated_at": "2025-01-13T20:16:58Z"
}

View File

@@ -168,20 +168,12 @@ def _csp(config):
def create_app(application):
application.config["FEATURE_BEST_PRACTICES_ENABLED"] = (
os.getenv("FEATURE_BEST_PRACTICES_ENABLED", "false").lower() == "true"
)
@application.context_processor
def inject_feature_flags():
feature_best_practices_enabled = application.config.get(
"FEATURE_BEST_PRACTICES_ENABLED", False
)
feature_about_page_enabled = application.config.get(
"FEATURE_ABOUT_PAGE_ENABLED", False
)
return dict(
FEATURE_BEST_PRACTICES_ENABLED=feature_best_practices_enabled,
FEATURE_ABOUT_PAGE_ENABLED=feature_about_page_enabled,
)

View File

@@ -0,0 +1,57 @@
document.addEventListener('DOMContentLoaded', () => {
const sidenavItems = document.querySelectorAll('.usa-sidenav__item > .parent-link');
let lastPath = window.location.pathname;
let debounceTimeout = null;
sidenavItems.forEach((link) => {
const parentItem = link.parentElement;
const sublist = parentItem.querySelector('.usa-sidenav__sublist');
const targetHref = link.getAttribute('href');
// initialize the menu to open the correct submenu based on the current route
if (window.location.pathname.startsWith(targetHref)) {
parentItem.classList.add('open');
link.setAttribute('aria-expanded', 'true');
}
link.addEventListener('click', (event) => {
const currentPath = window.location.pathname;
// prevent default behavior only if navigating to the same route
if (currentPath === targetHref) {
event.preventDefault();
return;
}
if (sublist && !parentItem.classList.contains('open')) {
// debounce the menu update to avoid flickering
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
parentItem.classList.add('open');
link.setAttribute('aria-expanded', 'true');
}, 50);
}
});
});
// handle browser back/forward navigation
window.addEventListener('popstate', () => {
const currentPath = window.location.pathname;
// sync menu state
sidenavItems.forEach((link) => {
const parentItem = link.parentElement;
const targetHref = link.getAttribute('href');
if (currentPath.startsWith(targetHref)) {
parentItem.classList.add('open');
link.setAttribute('aria-expanded', 'true');
} else {
parentItem.classList.remove('open');
link.setAttribute('aria-expanded', 'false');
}
});
lastPath = currentPath;
});
});

View File

@@ -198,8 +198,8 @@ td.table-empty-message {
word-wrap: break-word;
}
border: 1px solid color('gray-cool-10');
padding: units(2);
// border: 1px solid color('gray-cool-10');
// padding: units(2);
.tick-cross-list-permissions {
margin: units(1) 0;
@@ -852,21 +852,36 @@ $do-dont-top-bar-width: 1;
}
}
.linked-content:hover {
cursor: pointer;
transform: scale(1.05);
transition: transform 0.3s ease, background-color 0.3s ease;
}
.linked-card a {
text-decoration: none;
.usa-card__header, .usa-card__media {
@include at-media('tablet') {
padding-top: units(1);
}
}
&:visited {
color: color('ink');
}
&:focus .usa-card__container {
outline: units(2px) solid color('blue-40v');
outline-offset: 0.3rem;
}
&:hover .usa-card__container, &:focus .usa-card__container {
border-color: color('blue-60v');
background: color('blue-60v');
p, h3 {
color: white;
}
svg {
filter: brightness(0) invert(1);
}
}
li.linked-card:hover > div:first-child:hover {
border-color: #005ea2;
}
&.linked-content:hover, &.linked-content:focus {
cursor: pointer;
transition: transform 0.3s ease, background-color 0.3s ease;
}
li.linked-card:hover h4,
li.linked-card:hover p,
li.linked-card:hover svg,
.best-practices_card_img:hover {
color: #005ea2;
}
.best-practices_card_img {
@@ -876,10 +891,6 @@ li.linked-card:hover svg,
margin: 0 auto;
}
.best-practices_link {
text-decoration: none;
}
.usa-link--downloadable {
position: relative;
}
@@ -914,17 +925,25 @@ li.linked-card:hover svg,
mask-size: 1.75ex 1.75ex;
}
nav.nav {
position: sticky;
top: units(3);
}
.usa-sidenav__sublist {
display: none;
}
.usa-sidenav__item:hover .usa-sidenav__sublist,
.usa-sidenav__item:focus-within .usa-sidenav__sublist {
.usa-sidenav__item.open .usa-sidenav__sublist {
display: block;
}
.usa-sidenav__item a {
display: block;
.usa-sidenav__sublist .bold {
font-weight: bold;
}
.usa-sidenav__sublist li[role="menuitem"] {
border-top: 1px solid #dfe1e2;
}
.icon-list {

View File

@@ -87,9 +87,6 @@ class Config(object):
"tts-benefits-studio@gsa.gov",
],
}
FEATURE_BEST_PRACTICES_ENABLED = (
getenv("FEATURE_BEST_PRACTICES_ENABLED", "false") == "true"
)
FEATURE_ABOUT_PAGE_ENABLED = getenv("FEATURE_ABOUT_PAGE_ENABLED", "false") == "true"

View File

@@ -41,14 +41,8 @@ class ValidGovEmail:
if field.data == "":
return
from flask import url_for
message = "Enter a public sector email address."
message = """
Enter a public sector email address or
<a class="usa-link" href="{}">find out who can use Notify</a>
""".format(
url_for("main.features")
)
if not is_gov_user(field.data.lower()):
raise ValidationError(message)

View File

@@ -15,8 +15,6 @@ from app.main import main
from app.main.views.pricing import CURRENT_SMS_RATE
from app.main.views.sub_navigation_dictionaries import (
about_notify_nav,
best_practices_nav,
features_nav,
using_notify_nav,
)
from app.utils.user import user_is_logged_in
@@ -25,11 +23,6 @@ from app.utils.user import user_is_logged_in
# Hook to check for feature flags
@main.before_request
def check_feature_flags():
if request.path.startswith("/guides") and not current_app.config.get(
"FEATURE_BEST_PRACTICES_ENABLED", False
):
abort(404)
if request.path.startswith("/about") and not current_app.config.get(
"FEATURE_ABOUT_PAGE_ENABLED", False
):
@@ -40,8 +33,8 @@ def check_feature_flags():
def test_feature_flags():
return jsonify(
{
"FEATURE_BEST_PRACTICES_ENABLED": current_app.config[
"FEATURE_BEST_PRACTICES_ENABLED"
"FEATURE_ABOUT_PAGE_ENABLED": current_app.config[
"FEATURE_ABOUT_PAGE_ENABLED"
]
}
)
@@ -111,44 +104,6 @@ def callbacks():
return redirect(url_for("main.documentation"), 301)
# --- Features page set --- #
@main.route("/features")
@user_is_logged_in
def features():
return render_template("views/features.html", navigation_links=features_nav())
@main.route("/features/roadmap", endpoint="roadmap")
@user_is_logged_in
def roadmap():
return render_template("views/roadmap.html", navigation_links=features_nav())
@main.route("/features/sms")
@user_is_logged_in
def features_sms():
return render_template(
"views/features/text-messages.html", navigation_links=features_nav()
)
@main.route("/features/security", endpoint="security")
@user_is_logged_in
def security():
return render_template("views/security.html", navigation_links=features_nav())
@main.route("/features/using_notify")
@user_is_logged_in
def using_notify():
return (
render_template("views/using-notify.html", navigation_links=features_nav()),
410,
)
@main.route("/using-notify/delivery-status")
@user_is_logged_in
def message_status():
@@ -203,78 +158,75 @@ def trial_mode_new():
)
@main.route("/guides/best-practices")
@main.route("/using-notify/best-practices")
@user_is_logged_in
def best_practices():
return render_template(
"views/guides/best-practices.html",
navigation_links=best_practices_nav(),
navigation_links=using_notify_nav(),
)
@main.route("/guides/clear-goals")
@main.route("/using-notify/best-practices/clear-goals")
@user_is_logged_in
def clear_goals():
return render_template(
"views/guides/clear-goals.html",
navigation_links=best_practices_nav(),
navigation_links=using_notify_nav(),
)
@main.route("/guides/rules-and-regulations")
@main.route("/using-notify/best-practices/rules-and-regulations")
@user_is_logged_in
def rules_and_regulations():
return render_template(
"views/guides/rules-and-regulations.html",
navigation_links=best_practices_nav(),
navigation_links=using_notify_nav(),
)
@main.route("/guides/establish-trust")
@main.route("/using-notify/best-practices/establish-trust")
@user_is_logged_in
def establish_trust():
return render_template(
"views/guides/establish-trust.html",
navigation_links=best_practices_nav(),
navigation_links=using_notify_nav(),
)
@main.route("/guides/write-for-action")
@main.route("/using-notify/best-practices/write-for-action")
@user_is_logged_in
def write_for_action():
return render_template(
"views/guides/write-for-action.html",
navigation_links=best_practices_nav(),
navigation_links=using_notify_nav(),
)
@main.route("/guides/multiple-languages")
@main.route("/using-notify/best-practices/multiple-languages")
@user_is_logged_in
def multiple_languages():
return render_template(
"views/guides/multiple-languages.html",
navigation_links=best_practices_nav(),
navigation_links=using_notify_nav(),
)
@main.route("/guides/benchmark-performance")
@main.route("/using-notify/best-practices/benchmark-performance")
@user_is_logged_in
def benchmark_performance():
return render_template(
"views/guides/benchmark-performance.html",
navigation_links=best_practices_nav(),
navigation_links=using_notify_nav(),
)
@main.route("/guides/using-notify/guidance")
@main.route("/using-notify/guidance")
@user_is_logged_in
def guidance_index():
return render_template(
"views/guidance/index.html",
navigation_links=using_notify_nav(),
feature_best_practices_enabled=current_app.config[
"FEATURE_BEST_PRACTICES_ENABLED"
],
)
@@ -357,14 +309,12 @@ def studio():
# --- Redirects --- #
@main.route("/roadmap", endpoint="old_roadmap")
@main.route("/information-security", endpoint="information_security")
@main.route("/using_notify", endpoint="old_using_notify")
@main.route("/information-risk-management", endpoint="information_risk_management")
@main.route("/integration_testing", endpoint="old_integration_testing")
def old_page_redirects():
redirects = {
"main.old_roadmap": "main.roadmap",
"main.information_security": "main.using_notify",
"main.old_using_notify": "main.using_notify",
"main.information_risk_management": "main.security",

View File

@@ -1,89 +1,45 @@
from flask import current_app
def features_nav():
return [
{
"name": "Features",
"link": "main.features",
"sub_navigation_items": [
# {
# "name": "Text messages",
# "link": "main.features_sms",
# },
],
},
{
"name": "Roadmap",
"link": "main.roadmap",
},
{
"name": "Security",
"link": "main.security",
},
]
def using_notify_nav():
nav_items = [
{"name": "Get started", "link": "main.get_started"},
{"name": "Guides", "link": "main.best_practices"},
{
"name": "Best Practices",
"link": "main.best_practices",
"sub_navigation_items": [
{
"name": "Clear goals",
"link": "main.clear_goals",
},
{
"name": "Rules and regulations",
"link": "main.rules_and_regulations",
},
{
"name": "Establish trust",
"link": "main.establish_trust",
},
{
"name": "Write for action",
"link": "main.write_for_action",
},
{
"name": "Multiple languages",
"link": "main.multiple_languages",
},
{
"name": "Benchmark performance",
"link": "main.benchmark_performance",
},
],
},
{"name": "Trial mode", "link": "main.trial_mode_new"},
{"name": "Tracking usage", "link": "main.pricing"},
{"name": "Delivery Status", "link": "main.message_status"},
{"name": "Guidance", "link": "main.guidance_index"},
]
if not current_app.config.get("FEATURE_BEST_PRACTICES_ENABLED"):
nav_items = [
item for item in nav_items if item["link"] != "main.best_practices"
]
return nav_items
def best_practices_nav():
return [
{
"name": "Best Practices",
"link": "main.best_practices",
},
{
"name": "Clear goals",
"link": "main.clear_goals",
},
{
"name": "Rules and regulations",
"link": "main.rules_and_regulations",
},
{
"name": "Establish trust",
"link": "main.establish_trust",
"sub_navigation_items": [
{
"name": "Get the word out",
"link": "main.establish_trust#get-the-word-out",
},
{
"name": "As people receive texts",
"link": "main.establish_trust#as-people-receive-texts",
},
],
},
{
"name": "Write for action",
"link": "main.write_for_action",
},
{
"name": "Multiple languages",
"link": "main.multiple_languages",
},
{
"name": "Benchmark performance",
"link": "main.benchmark_performance",
},
]
def about_notify_nav():
return [
{
@@ -93,20 +49,6 @@ def about_notify_nav():
{
"name": "Why text messaging",
"link": "main.why_text_messaging",
"sub_sub_navigation_items": [
{
"name": "Reach people using a common method",
"link": "main.why_text_messaging#reach-people-using-a-common-method",
},
{
"name": "Improve customer experience",
"link": "main.why_text_messaging#improve-customer-experience",
},
{
"name": "What texting is best for",
"link": "main.why_text_messaging#what-texting-is-best-for",
},
],
},
{
"name": "Security",

View File

@@ -40,12 +40,6 @@ class HeaderNavigation(Navigation):
"support": {
"support",
},
"features": {
"features",
"features_sms",
"roadmap",
"security",
},
"best_practices": {
"best_practices",
"clear_goals",
@@ -57,7 +51,6 @@ class HeaderNavigation(Navigation):
},
"using_notify": {
"get_started",
"using_notify",
"pricing",
"trial_mode_new",
"message_status",

View File

@@ -56,28 +56,19 @@
{% for item in navigation_links %}
<li class="usa-sidenav__item">
<a href="{{ url_for(item.link) }}"
class="parent-link {% if item['link'] == request.endpoint %} usa-current {% endif %}"
aria-haspopup="true" aria-expanded="false">
class="parent-link {% if request.endpoint.startswith(item['link']) or item.sub_navigation_items | selectattr('link', 'equalto', request.endpoint) | list | length > 0 %} usa-current {% endif %}"
aria-haspopup="true"
aria-expanded="{{ 'true' if request.endpoint.startswith(item['link']) else 'false' }}">
{{ item.name }}
</a>
{% if item.sub_navigation_items %}
<ul class="usa-sidenav__sublist" role="menu">
{% for sub_item in item.sub_navigation_items %}
<li role="menuitem">
<a href="{{ url_for(sub_item.link.split('#')[0]) }}#{{ sub_item.link.split('#')[1] }}">
<a href="{{ url_for(sub_item.link.split('#')[0]) }}#{{ sub_item.link.split('#')[1] }}"
class="{% if request.endpoint == sub_item['link'] %}usa-current bold{% endif %}">
{{ sub_item.name }}
</a>
{% if sub_item.sub_sub_navigation_items %}
<ul class="usa-sidenav__sublist usa-sidenav__sub-sublist" role="menu">
{% for sub_sub_item in sub_item.sub_sub_navigation_items %}
<li role="menuitem">
<a href="{{ url_for(sub_sub_item.link.split('#')[0]) }}#{{ sub_sub_item.link.split('#')[1] }}">
{{ sub_sub_item.name }}
</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
@@ -85,6 +76,8 @@
</li>
{% endfor %}
</ul>
</nav>
</div>
<div class="tablet:grid-col-10 tablet:padding-left-4 usa-prose site-prose">

View File

@@ -1,18 +1,17 @@
<ul class="usa-card-group">
<ul class="usa-card-group grid-row flex-row">
{% for item in card_contents %}
<li class="usa-card grid-col tablet:grid-col-4 {% if item.link %}linked-card linked-content{% endif %}">
<li class="usa-card grid-col mobile-lg:grid-col-12 tablet:grid-col-6 desktop:grid-col-4 {% if item.link %}linked-card linked-content{% endif %}">
{% if item.link %}
<a href="{{ item.link }}">
{% endif %}
<div class="usa-card__container">
{% if item.card_heading %}
<div class="usa-card__header">
{% if item.link %}
<a href="{{ item.link }}">
{% endif %}
<h3
class="usa-card__heading {% if text_align != 'left' or text_align is not defined %}text-center{% else %}text-left{% endif %} {% if item.link %}linked-card{% endif %}">
{{ item.card_heading }}
</h3>
{% if item.link %}
</a>
{% endif %}
</div>
{% endif %}
@@ -37,6 +36,9 @@
</div>
{% endif %}
</div>
{% if item.link %}
</a>
{% endif %}
</li>
{% endfor %}
</ul>

View File

@@ -1,15 +1,12 @@
{% set is_about_page = request.path.startswith('/about') %}
{% set is_join_notify_page = request.path.startswith('/join-notify') %}
{% set is_contact_page = request.path.startswith('/contact') %}
{% set is_information_section = is_about_page or is_join_notify_page or is_contact_page %}
{% if current_user.is_authenticated %}
{% 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')}
] %}
{% if FEATURE_BEST_PRACTICES_ENABLED %}
{% set navigation = navigation + [{"href": url_for('main.best_practices'), "text": "Guides", "active": header_navigation.is_selected('best_practices')}] %}
{% endif %}
{% set navigation = navigation + [
{"href": url_for('main.features'), "text": "Features", "active": header_navigation.is_selected('features')},
{"href": url_for("main.show_accounts_or_dashboard"), "text": "Current service", "active": request.path.startswith('/accounts')},
{"href": url_for('main.get_started'), "text": "Using Notify", "active": request.path.startswith('/using-notify')},
{"href": url_for('main.support'), "text": "Contact us", "active": header_navigation.is_selected('support')}
] %}
@@ -35,16 +32,17 @@
{% endif %}
{% else %}
{% set navigation = [] %}
{% if FEATURE_ABOUT_PAGE_ENABLED %}
{% set navigation = navigation + [
{"href": url_for('main.about_notify'), "text": "About Notify", "active": request.path == '/about'},
{"href": url_for('main.join_notify'), "text": "Join Notify", "active": request.path == '/join-notify'},
{"href": url_for('main.contact'), "text": "Contact us", "active": request.path == '/contact'}
{% set navigation = [
{"href": url_for('main.about_notify'), "text": "About Notify", "active": is_about_page},
{"href": url_for('main.join_notify'), "text": "Join Notify", "active": is_join_notify_page},
{"href": url_for('main.contact'), "text": "Contact us", "active": is_contact_page}
] %}
{% endif %}
{% endif %}
<header class="usa-header usa-header--extended">
@@ -63,7 +61,7 @@
<button type="button" class="usa-menu-btn">Menu</button>
{% endif %}
</div>
{% if not current_user.is_authenticated and FEATURE_ABOUT_PAGE_ENABLED and request.path == '/about'%}
{% if not current_user.is_authenticated and is_information_section%}
<div class="usa-nav__login">
<a class="usa-button usa-button login-button login-button--primary margin-right-2"
href="{{ initial_signin_url }}">Sign

View File

@@ -11,10 +11,8 @@
<h1 class="font-body-2xl">Features</h1>
<p class="usa-body">If you work for the government, you can use Notify.gov to keep your users updated.</p>
<p class="usa-body">Notify makes it easy to create, customize, and send <a class="usa-link" href="{{ url_for('main.features_sms') }}">text messages</a>.</p>
<!-- <ul class="list list-bullet">
<li><a class="usa-link" href="{{ url_for('main.features_sms') }}">text messages</a></li>
</ul> -->
<p class="usa-body">Notify makes it easy to create, customize, and send text messages</a>.</p>
<p class="usa-body">You do not need any technical knowledge to use Notify.</p>
{% if not current_user.is_authenticated %}
<p class="usa-body"><a class="usa-link" href="{{ url_for('main.register') }}">Create an account</a> for free and try it yourself.</p>

View File

@@ -8,245 +8,103 @@
{% endblock %}
{% block content_column_content %}
<h1 class="font-body-2xl margin-bottom-3">Guidance</h1>
<h1 class="font-body-2xl margin-bottom-3">Guidance</h1>
<p>Notify allows you to easily create templates for messages for your recipients. You can customize messages to encourage
your recipient to manage their benefits and increase follow-through.</p>
<p>Below we explain how to:</p>
<p>Notify allows you easily to create templates for messages for your recipients. You can customize messages to encourage
your recipient to manage their benefits and increase follow through.</p>
<p>Below we explain how to:</p>
<ul class="list list-bullet">
<li><a class="usa-link" href="#personalize-content">Personalize your content</a></li>
<li><a class="usa-link" href="#conditional-content">Add conditional content</a></li>
</ul>
<ul class="list list-bullet">
{% if not feature_best_practices_enabled %}
<li><a class="usa-link" href="#format-content">Format your content</a></li>
<li><a class="usa-link" href="#add-links">Add links</a></li>
{% endif %}
<li><a class="usa-link" href="#personalize-content">Personalize your content</a></li>
<li><a class="usa-link" href="#conditional-content">Add conditional content</a></li>
<li><a class="usa-link" href="#indentify-program">Identify your program</a></li>
<li><a class="usa-link" href="#prepare-data">Prepare your data</a></li>
{% if not feature_best_practices_enabled %}
<li><a class="usa-link" href="#prevent-fraud">Prevent fraud</a></li>
{% endif%}
</ul>
{# Format content #}
<h2 class="padding-top-1" id="format-content">Format your content</h2>
{% if not feature_best_practices_enabled %}
<p>Effective texts will help your message recipients take the steps needed to secure and keep the benefits and services
they depend on. To craft an effective text:</p>
<ul class="list list-bullet">
<li>Choose your messages thoughtfully. Text messages are best to remind/nudge someone to take a specific action or
communicate information at a particular time rather than pushing out broad or overly general information.</li>
<li>Clearly state the issue and expected response. Tell your recipient what you expect from them.</li>
<li>Say only one important thing per message.</li>
<li>Frame the message to encourage action and explain the consequences of not completing the desired action.</li>
</ul>
{% endif %}
<h3>To create and format your message</h3>
<ol class="list">
<li>All messages start from a template</li>
<li>Click "<a href={{ url_for('.choose_template', service_id=current_service.id) }}>Send Messages</a>". You'll see existing templates.</li>
<li>Add a new template or choose an existing template and select Edit.</li>
</ol>
{% if not feature_best_practices_enabled %}
{# Add links #}
<h2 class="padding-top-1" id="add-links">Add links</h2>
<p>When composing a text message, links to websites or online applications can help your recipient respond quickly.</p>
<ul class="list list-bullet">
<li>Write URLs in full and Notify will convert them into links for you. Note that you cannot hyperlink text in Notify
messages.</li>
<li>For link click tracking, you can consider adding campaign parameters to URLs.</li>
<li>All links should point to a government domain.</li>
<li>Link directly to where your recipient needs to take action, not to more information.</li>
</ul>
<h3>About link-shortening services</h3>
<p>We do not recommend using a third-party link-shortening service because:</p>
<ul class="list list-bullet">
<li>Your recipient cannot see where the link will take them, which could make them suspect the link is spam/scam.</li>
<li>Your link might stop working if theres a service outage.</li>
<li>You can no longer control where the redirect goes.</li>
</ul>
{% endif %}
{# Personalize content #}
<h2 class="padding-top-1" id="personalize-content">Personalize your content</h2>
<p>Personalizing your content can increase response rates and help the recipient know the text is legitimate.</p>
<ul class="list list-bullet">
<li>Including a person's first name increases response rates.</li>
<li>Specific details such as time and location of an appointment or where suspected fraud use occurred encourages action.</li>
</ul>
<h3>To personalize your content</h3>
<ol class="list">
<li>Add a placeholder to your content by placing two brackets around the personalized elements.</li>
<li>You can manually enter the personalized content or you can upload a spreadsheet with the details and let Notify do the
work for you. See <a href="#prepare-data">data preparation</a>.</li>
</ol>
<h4>Example</h4>
<p>To personalize with the recipient's first name and include a reference number:</p>
<p class="padding-2 bg-base-lightest">State WIC: Hello ((first name)), your reference is ((ref number)). Please provide this number when you call 555-123-1234 to make an appointment.</p>
<p>Note that variations in the length of personalized content can impact the length of specific messages, and may affect
the number of parts used.</p>
{# Add conditional content #}
<h2 class="padding-top-1" id="conditional-content">Add conditional content</h2>
<p>Conditional (or optional) content appears only when a recipient meets certain criteria. This feature allows you to make
all or part of the message contingent upon specific criteria associated with the recipient.</p>
<h3>To add conditional content</h3>
<ol class="list">
<li>Use two brackets and ?? to define the conditional content.</li>
<li>You can manually enter the conditional content or you can upload a spreadsheet with the personal details and let Notify
do the work for you. See <a href="#prepare-data">data preparation</a>.</li>
</ol>
<h4>Examples</h4>
<ol class="list">
<li>If you only want to show something to people who are under 18:
</br>
<p class="padding-2 bg-base-lightest">State SNAP: Renewal applications are due by March 15. ((under18??Please get your application signed by a parent or
guardian.))</p>
</li>
<li>
If you want to make people who are homebound aware of the option of virtual visits (but not other message recipients):
</br>
<p class="padding-2 bg-base-lightest">State Medicaid: Please call 555-123-1234 to schedule an appointment. ((homebound??Virtual visits are available.))</p>
</li>
<li>
If you want to send a messages in different languages to different recipients:
</br>
<p class="padding-2 bg-base-lightest">((English??Weve identified unauthorized use on your EBT account. Call the phone number on the back of your card to
cancel or go to your local CSO for immediate replacement.))((Spanish??Hemos identificado un uso no autorizado en su
cuenta EBT. Llame al número de teléfono que aparece en el reverso de su tarjeta para cancelarla o diríjase a su CSO
local para que se la sustituyan inmediatamente.))</p>
</li>
</ol>
{# Format content #}
<h2 class="padding-top-1" id="format-content">Format your content</h2>
{# Identify your program #}
<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>
<h3>To create and format your message</h3>
<ol class="list">
<li>All messages start from a template</li>
<li>Click "<a href={{ url_for('.choose_template', service_id=current_service.id) }}>Send Messages</a>". You'll see existing templates.</li>
<li>Add a new template or choose an existing template and select Edit.</li>
</ol>
<h3>To customize your program name</h3>
<p>To change the text message sender from the default service name:</p>
<ol class="list">
<li>Go to the <a href="{{ url_for('main.service_settings', service_id=current_service.id) }}">Settings page</a></li>
<li>Select “Start text messages with service name.”</li>
<li>Change the service name to a familiar program or benefit.</li>
</ol>
{# Add links #}
<p>When composing a text message, links to websites or online applications can help your recipient respond quickly.</p>
{# Prepare your data #}
<h2 class="padding-top-1" id="prepare-data">Prepare your data</h2>
<p>The easiest and most efficient way to personalize your content is by uploading a spreadsheet. Notify can accommodate
many file formats and structures.</p>
<ul class="list list-bullet">
<li>Write URLs in full and Notify will convert them into links for you. Note that you cannot hyperlink text in Notify
messages.</li>
<li>For link click tracking, you can consider adding campaign parameters to URLs.</li>
<li>All links should point to a government domain.</li>
<li>Link directly to where your recipient needs to take action, not to more information.</li>
</ul>
<h3>File format</h3>
<p>Notify can accept files in the following formats: CSV, TSV, ODS, and Microsoft Excel.</p>
<h3>About link-shortening services</h3>
<p>We do not recommend using a third-party link-shortening service because:</p>
<ul class="list list-bullet">
<li>Your recipient cannot see where the link will take them, which could make them suspect the link is spam/scam.</li>
<li>Your link might stop working if theres a service outage.</li>
<li>You can no longer control where the redirect goes.</li>
</ul>
<h3>File structure</h3>
<ul class="list">
<li>The phone number must be in the first column (Column A) and must be labeled <strong>Phone number</strong>.</li>
<li>Each column must have a unique name.</li>
<li>If you are using Excel, you must either disable the”auto-date/time format” for time and date columns or convert the file
to a CSV prior to loading. (If you do not, Excel will display date/time data in a confusing format.)</li>
</ul>
{# Personalize content #}
<h2 class="padding-top-1" id="personalize-content">Personalize your content</h2>
<p>Personalizing your content can increase response rates and help the recipient know the text is legitimate.</p>
<ul class="list list-bullet">
<li>Including a person's first name increases response rates.</li>
<li>Specific details such as time and location of an appointment or where suspected fraud use occurred encourages action.</li>
</ul>
<h3>Formatting personalized content</h3>
<p>If you are sending a message with personalized content, such as the first name of the recipient or the appointment time
and location, the names of the column headings have to match the indicator included in the message template.</p>
<p>For example, if the personalized content is the first name of the recipient, and we are using the spreadsheet below, the
indicator in the message needs to be ((First name)), not ((firstname)) or ((name)).</p>
<h3>To personalize your content</h3>
<ol class="list">
<li>Add a placeholder to your content by placing two brackets around the personalized elements.</li>
<li>You can manually enter the personalized content or you can upload a spreadsheet with the details and let Notify do the
work for you. See <a href="#prepare-data">data preparation</a>.</li>
</ol>
<h3>Formatting conditional content</h3>
<p>If you are sending messages with conditional content, such as content based on the recipients preferred language or
location, the flag to receive the content needs to be captured in its own column with a Yes or No (Y/N) flag.</p>
<h4>Example</h4>
<p>To personalize with the recipient's first name and include a reference number:</p>
<p class="padding-2 bg-base-lightest">State WIC: Hello ((first name)), your reference is ((ref number)). Please provide this number when you call 555-123-1234 to make an appointment.</p>
<h3>Example</h3>
<div class="table-overflow-x-auto">
<table class="usa-table">
<caption class="usa-sr-only">
Example
</caption>
<thead>
<tr>
<th scope="col">Phone number</th>
<th scope="col">First name</th>
<th scope="col">Last name</th>
<th scope="col">Spanish</th>
<th scope="col">English</th>
<th scope="col">Date</th>
<th scope="col">Time</th>
<th scope="col">Location</th>
</tr>
</thead>
<tbody>
<tr>
<td>123-456-7890</td>
<td>Lulu</td>
<td>Praether</td>
<td>N</td>
<td>Y</td>
<td>November 2, 2023</td>
<td>3:25</td>
<td>123 Ford Rd.</td>
</tr>
<tr>
<td>234-567-8901</td>
<td>Sela</td>
<td>Appel</td>
<td>Y</td>
<td>N</td>
<td>November 2, 2023</td>
<td>4:00</td>
<td>123 Ford Rd.</td>
</tr>
<tr>
<td>123-456-7890</td>
<td>Dexter</td>
<td>Moseley</td>
<td>N</td>
<td>Y</td>
<td>November 2, 2023</td>
<td>2:00</td>
<td>123 Ford Rd.</td>
</tr>
</tbody>
</table>
</div>
<p>Note that variations in the length of personalized content can impact the length of specific messages, and may affect
the number of parts used.</p>
{% if not feature_best_practices_enabled %}
{# Preventing fraud #}
<h2 class="padding-top-1" id="prevent-fraud">Preventing fraud</h2>
<p>Texting fraud is ever prevalent and while we cant eliminate the risk, we can reduce the chances that recipients will
fall victim to fraud.</p>
<ol class="list">
<li>Never send unnecessary or protected private information.</li>
<li>Remind your recipients that text messaging is not a secure means of communication and they should not send you private
information via text.</li>
<li>Only include links to websites and online applications that are secure.</li>
<li>Send an introductory text letting recipients know you will be texting them and to save the number in their phone.</li>
<li>Include an auto-response with the name of your organization and a contact phone number.</li>
</ol>
{# Add conditional content #}
<h2 class="padding-top-1" id="conditional-content">Add conditional content</h2>
<p>Conditional (or optional) content appears only when a recipient meets certain criteria. This feature allows you to make
all or part of the message contingent upon specific criteria associated with the recipient.</p>
<h3>To add conditional content</h3>
<ol class="list">
<li>Use two brackets and ?? to define the conditional content.</li>
<li>You can manually enter the conditional content or you can upload a spreadsheet with the personal details and let Notify
do the work for you. See <a href="#prepare-data">data preparation</a>.</li>
</ol>
<h4>Examples</h4>
<ol class="list">
<li>If you only want to show something to people who are under 18:
</br>
<p class="padding-2 bg-base-lightest">State SNAP: Renewal applications are due by March 15. ((under18??Please get your application signed by a parent or
guardian.))</p>
</li>
<li>
If you want to make people who are homebound aware of the option of virtual visits (but not other message recipients):
</br>
<p class="padding-2 bg-base-lightest">State Medicaid: Please call 555-123-1234 to schedule an appointment. ((homebound??Virtual visits are available.))</p>
</li>
<li>
If you want to send a messages in different languages to different recipients:
</br>
<p class="padding-2 bg-base-lightest">((English??Weve identified unauthorized use on your EBT account. Call the phone number on the back of your card to
cancel or go to your local CSO for immediate replacement.))((Spanish??Hemos identificado un uso no autorizado en su
cuenta EBT. Llame al número de teléfono que aparece en el reverso de su tarjeta para cancelarla o diríjase a su CSO
local para que se la sustituyan inmediatamente.))</p>
</li>
</ol>
...
<h3>Examples</h3>
<ol class="list">
<li>Introductory text:
</br>
<p class="padding-2 bg-base-lightest">State Transit Dept: We're piloting a new way to get important reminders to our staff. Save this number to your phone,
you'll receive updates from us here.</p>
</li>
<li>Auto-response text:
</br>
<p class="padding-2 bg-base-lightest">State Agency: This number is unmonitored. To contact us, call us at 555-123-1234. We will never ask for personal details
in a text. If you have questions about how to protect your privacy, see statename.gov/privacy.</p>
</li>
</ol>
{% endif %}
{% endblock %}

View File

@@ -8,7 +8,7 @@
{% endblock %}
{% block content_column_content %}
{{ breadcrumbs.breadcrumb(page_title, "Guides", "main.best_practices") }}
{{ breadcrumbs.breadcrumb(page_title, "Best Practices", "main.best_practices") }}
<section class="usa-prose">
<h1>{{page_title}}</h1>
@@ -27,58 +27,46 @@
quantity of messages delivered, how people engage with messages, and how they take action.
</p>
<h3 class="padding-left-5">Message delivery</h3>
<p class="font-body-lg text-light padding-left-5">Benchmark: <span class="text-bold">80%</span> of texts are
<h3>Message delivery</h3>
<p class="text-light">Benchmark: <span class="text-bold">80%</span> of texts are
successfully delivered
</p>
<div class="padding-left-2 measure-4">
<ul>
<li>
<p>
You may discover that some numbers are temporarily or permanently unavailable due to service being
discontinued,
numbers changing, or being a landline.
</p>
</li>
</ul>
</div>
<ul>
<li>
<p>
You may discover that some numbers are temporarily or permanently unavailable due to service being
discontinued,
numbers changing, or being a landline.
</p>
</li>
</ul>
<h3>Engagement</h3>
<p class="text-light">Benchmark: Engagement rates with texts ranged from <span
class="text-bold">17%</span>
to <span class="text-bold">26%</span></p>
<ul>
<li>
<p>
The highest rate of engagement with a text comes within hours of sending. Engagement rates include any kind
of
action taken due to a text, including replying “STOP” to prevent future texts.
</p>
</li>
</ul>
<h3 class="padding-left-5">Engagement</h3>
<div class="measure-4 padding-left-5">
<p class="font-body-lg text-light">Benchmark: Engagement rates with texts ranged from <span
class="text-bold">17%</span>
to <span class="text-bold">26%</span></p>
</div>
<div class="padding-left-2 measure-4">
<ul>
<li>
<p>
The highest rate of engagement with a text comes within hours of sending. Engagement rates include any kind
of
action taken due to a text, including replying “STOP” to prevent future texts.
</p>
</li>
</ul>
</div>
<h3 class="padding-left-5">Appointment requests</h3>
<div class="measure-4 padding-left-5">
<p class="font-body-lg text-light">Benchmark: Requesting appointments after receiving texts ranged
<h3>Appointment requests</h3>
<p class="text-light">Benchmark: Requesting appointments after receiving texts ranged
from <span class="text-bold">4%</span> to <span class="text-bold">9%</span></p>
</div>
<div class="padding-left-2 measure-4">
<ul>
<li>
<p>
Requesting appointments is a specific type of engagement. Provide a phone number or link to an online
appointment
request form.
</p>
</li>
</ul>
</div>
<ul>
<li>
<p>
Requesting appointments is a specific type of engagement. Provide a phone number or link to an online
appointment
request form.
</p>
</li>
</ul>
<p>
The <a class="use-link usa-link--external" href="https://codeforamerica.org/resources/texting-playbook/"
target="_blank">Code for Americas Texting
@@ -86,63 +74,42 @@
reported specific learnings around appointment reminders, completing
document submission, and maintenance reminders.
</p>
<h3 class="padding-left-5">Appointment reminders</h3>
<div class="measure-4 padding-left-5">
<p class="font-body-lg text-light">Benchmark: Clients were <span class="text-bold">79%</span> more
likely
to keep
their appointment after receiving a text reminder.</p>
</div>
<div class="padding-left-2 measure-4">
<ul>
<li>
<p>You will likely see more completed appointments.</p>
</li>
</ul>
</div>
<h3>Appointment reminders</h3>
<p class="text-light">Benchmark: Clients were <span class="text-bold">79%</span> more
likely to keep their appointment after receiving a text reminder.</p>
<ul>
<li>
<p>You will likely see more completed appointments.</p>
</li>
</ul>
<p class="text-light">Benchmark: Clients were <span class="text-bold">55%</span> more
likely to complete an interview after receiving an interview reminder</p>
<ul>
<li>
<p>You will likely see more completed interviews.</p>
</li>
</ul>
<div class="measure-4 padding-left-5">
<p class="font-body-lg text-light">Benchmark: Clients were <span class="text-bold">55%</span> more
likely
to complete
an interview after receiving an interview reminder</p>
</div>
<div class="padding-left-2 measure-4">
<ul>
<li>
<p>You will likely see more completed interviews.</p>
</li>
</ul>
</div>
<h3 class="padding-left-5">Document submission</h3>
<div class="measure-4 padding-left-5">
<p class="font-body-lg text-light">
<h3>Document submission</h3>
<p class="text-light">
Benchmark: Clients were <span class="text-bold">6%</span> more likely to complete document submission after
receiving a customized list of required documents via text
</p>
</div>
<div class="padding-left-2 measure-4">
<ul>
<li>
<p>To encourage response, provide a custom list of the needed documents and information about how to submit
them.
</p>
</li>
</ul>
</div>
<ul>
<li>
<p>To encourage response, provide a custom list of the needed documents and information about how to submit
them.
</p>
</li>
</ul>
<h3 class="padding-left-5">Reminders</h3>
<div class="measure-4 padding-left-5">
<p class="font-body-lg text-light">Benchmark: Text reminders improved case maintenance rates by <span
class="text-bold">21%</span></p>
</div>
<div class="padding-left-2 measure-4">
<ul>
<li>
<p>You may see less turnover in your case rates.</p>
</li>
</ul>
</div>
<h3 class="font-body-xl">Reminders</h3>
<p class="text-light">Benchmark: Text reminders improved case maintenance rates by <span
class="text-bold">21%</span></p>
<ul>
<li>
<p>You may see less turnover in your case rates.</p>
</li>
</ul>
</section>
{% endblock %}

View File

@@ -15,7 +15,7 @@
</p>
<p>This set of best practices will help you get an effective texting initiative up and running.</p>
<h2 class="padding-bottom-2">
<h2 class="padding-bottom-4">
Key elements of a texting campaign
</h2>
@@ -24,37 +24,37 @@
"svg_src": "goal",
"card_heading": "Establish clear goals",
"p_text": "Start with a singular purpose. Make explicit what you want to achieve.",
"link": "/guides/clear-goals"
"link": "/using-notify/best-practices/clear-goals"
},
{
"svg_src": "compliant",
"card_heading": "Follow rules & regulations",
"p_text": "Understand what is required when texting the public.",
"link": "/guides/rules-and-regulations"
"link": "/using-notify/best-practices/rules-and-regulations"
},
{
"svg_src": "trust",
"card_heading": "Establish trust",
"p_text": "Help your audience anticipate and welcome your texts.",
"link": "/guides/establish-trust"
"link": "/using-notify/best-practices/establish-trust"
},
{
"svg_src": "runner",
"card_heading": "Write texts that provoke action",
"p_text": "Help your audience know what to do with the information you send.",
"link": "/guides/write-for-action"
"link": "/using-notify/best-practices/write-for-action"
},
{
"svg_src": "language",
"card_heading": "Send texts in multiple languages",
"p_text": "What to know as you plan translated texts.",
"link": "/guides/multiple-languages"
"link": "/using-notify/best-practices/multiple-languages"
},
{
"svg_src": "chart",
"card_heading": "Measure performance with benchmarking",
"p_text": "Learn how effective your texting program can be.",
"link": "/guides/benchmark-performance"
"link": "/using-notify/best-practices/benchmark-performance"
}
] %}

View File

@@ -8,7 +8,7 @@
{% endblock %}
{% block content_column_content %}
{{ breadcrumbs.breadcrumb(page_title, "Guides", "main.best_practices") }}
{{ breadcrumbs.breadcrumb(page_title, "Best Practices", "main.best_practices") }}
<section class="usa-prose">
<h1>{{page_title}}</h1>
@@ -162,7 +162,7 @@
</div>
<p>
Review your drafted hypothesis with your team to make sure everyone is aligned on your desired goals. A clear and
concise hypothesis can help you decide how to <a href="../guides/write-for-action">write text message
concise hypothesis can help you decide how to <a href="../best-practices/write-for-action">write text message
content
that provokes action</a>.
</p>

View File

@@ -10,7 +10,7 @@
{% endblock %}
{% block content_column_content %}
{{ breadcrumbs.breadcrumb(page_title, "Guides", "main.best_practices") }}
{{ breadcrumbs.breadcrumb(page_title, "Best Practices", "main.best_practices") }}
<section class="usa-prose">
<h1>{{page_title}}</h1>

View File

@@ -8,7 +8,7 @@
{% endblock %}
{% block content_column_content %}
{{ breadcrumbs.breadcrumb(page_title, "Guides", "main.best_practices") }}
{{ breadcrumbs.breadcrumb(page_title, "Best Practices", "main.best_practices") }}
<section class="usa-prose">
<h1>{{page_title}}</h1>

View File

@@ -8,7 +8,7 @@
{% endblock %}
{% block content_column_content %}
{{ breadcrumbs.breadcrumb(page_title, "Guides", "main.best_practices") }}
{{ breadcrumbs.breadcrumb(page_title, "Best Practices", "main.best_practices") }}
<section class="usa-prose">
<h1>{{page_title}}</h1>
@@ -22,7 +22,7 @@
If you do need expressed consent, consider including a pre-checked plain language opt-in (i.e. “Its OK to text
me.”) on
digital forms. Be sure to ask for an up-to-date phone number and include a question about the recipients preferred
language for text messages if you expect to <a href="../guides/multiple-languages">translate your text
language for text messages if you expect to <a href="../best-practices/multiple-languages">translate your text
messages</a> in
languages other than English.
</p>
@@ -81,7 +81,7 @@
<h3>Opting out</h3>
<p>
There is no policy requirement for senders to communicate opt-out options, but <a
href="../guides/establish-trust#as-people-receive-texts"> including instructions in introductory and/or
href="../best-practices/establish-trust#as-people-receive-texts"> including instructions in introductory and/or
auto-response texts </a> on how to opt out and opt back in are effective ways to establish trust with your
audience.
</p>

View File

@@ -9,7 +9,7 @@
{% endblock %}
{% block content_column_content %}
{{ breadcrumbs.breadcrumb(page_title, "Guides", "main.best_practices") }}
{{ breadcrumbs.breadcrumb(page_title, "Best Practices", "main.best_practices") }}
<section class="usa-prose">
<h1>{{page_title}}</h1>

View File

@@ -77,7 +77,7 @@
<p>Sometimes Notify receives more detailed information from the carriers on the status of messages, and these can be found
in the downloadable reports. Not all carriers provide the same level of detail regarding delivery and some delivery
statutes have a slight variation in word choice. Notify includes this information in the reports to provide you as much
detail as possible. Remember, for <a class="usa-link" href="/features/security">security</a> purposes, detailed information is only available for seven days after a
detail as possible. Remember, for security purposes, detailed information is only available for seven days after a
message has been sent.</p>
<h2 class="heading-medium">Opting out</h2>

View File

@@ -1,87 +0,0 @@
{% extends "base.html" %}
{% from "components/table.html" import mapping_table, row, text_field, edit_field, field with context %}
{% from "components/content-metadata.html" import content_metadata %}
{% block per_page_title %}
Roadmap
{% endblock %}
{% block content_column_content %}
<h1 class="font-body-2xl margin-bottom-3">Roadmap</h1>
<!-- {{ content_metadata(
data={
"Last updated": "1 November 2022",
"Next review due on": "15 December 2022"
}
) }} -->
<p>The Notify roadmap shows what were working on and what we're planning to do next.</p>
<p>This roadmap is only a guide. It does not cover everything we do, and some things may change.</p>
<p>You can <a class="usa-link" href="{{url_for('.support')}}">contact us</a> if you have any questions about the roadmap or suggestions for new features.</p>
<h2 id="things-we-are-working-on">What were working on</h2>
<h3 id="now">Now</h3>
<p>We are investigating the Notify concept, building on the notifications tool pioneered by the UK.</p>
<p>To do this, we are convening a pilot with a small set of partners.</p>
<p>Goals during this stage:</p>
<ul class="list list-bullet">
<li>Achieve compliance to begin piloting, such as ATO and privacy standards.
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
<use xlink:href="/assets/img/sprite.svg#check"></use>
</svg>
</li>
<li>Demonstrate that a government-run notifications tool provides a unique value.</li>
<li>Gather data from the pilot to improve the product.</li>
</ul>
<p>Features prioritized during this stage:</p>
<ul class="list list-bullet">
<li>Bulk, individually customizable one-way SMS sending via web UI</li>
<li>Organization permissions settings for various team members to edit/send</li>
<li>Reusable message templates</li>
<li>Seven-day records deletion</li>
<li>Message send/failure analytics</li>
</ul>
<h3 id="next">Next</h3>
<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>
<ul class="list list-bullet">
<li>Complement Notify with practical guidance and support services.</li>
<li>Iterate on existing features and implement new features based on what we've learned so far.</li>
<li>Hone our measurement approaches to better quantify impact.</li>
</ul>
<p>Features prioritized during this stage:</p>
<ul class="list">
<li>SMS sending via API integration</li>
<li>Self-service account creation</li>
<li>Application status page</li>
<li>Improved scheduled send option</li>
</ul>
<h3 id="later">Later</h3>
<p>In the future, we may decide to expand beyond SMS, or to offer the service government-wide.</p>
<p>Features to be considered during this stage:</p>
<ul class="list">
<li>Two-way messaging</li>
<li>Multilingual interface and content library options</li>
<li>Recurring scheduled send (such as “Send each Monday for 3 weeks”)</li>
</ul>
{% endblock %}

View File

@@ -21,7 +21,6 @@
<ul class="list list-bullet">
<li><a class="usa-link" href="{{ url_for('main.trial_mode_new') }}">trial mode</a></li>
<li><a class="usa-link" href="{{ url_for('main.message_status') }}">message status types</a></li>
<li><a class="usa-link" href="{{ url_for('main.features_sms') }}">text message replies</a></li>
</ul>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 KiB

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 KiB

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 KiB

After

Width:  |  Height:  |  Size: 927 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 KiB

After

Width:  |  Height:  |  Size: 480 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 KiB

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 448 KiB

View File

@@ -7,4 +7,3 @@ cloud_dot_gov_route: notify-demo.app.cloud.gov
redis_enabled: 1
nr_agent_id: '1134302465'
nr_app_id: '1083160688'
FEATURE_BEST_PRACTICES_ENABLED: true

View File

@@ -7,4 +7,3 @@ cloud_dot_gov_route: notify.app.cloud.gov
redis_enabled: 1
nr_agent_id: '1050708682'
nr_app_id: '1050708682'
FEATURE_BEST_PRACTICES_ENABLED: false

View File

@@ -12,4 +12,3 @@ SECRET_KEY: sandbox-notify-secret-key
nr_agent_id: ''
nr_app_id: ''
NR_BROWSER_KEY: ''
FEATURE_BEST_PRACTICES_ENABLED: true

View File

@@ -7,4 +7,3 @@ cloud_dot_gov_route: notify-staging.app.cloud.gov
redis_enabled: 1
nr_agent_id: '1134291385'
nr_app_id: '1031640326'
FEATURE_BEST_PRACTICES_ENABLED: false

View File

@@ -81,6 +81,8 @@ const javascripts = () => {
paths.src + 'javascripts/main.js',
paths.src + 'javascripts/totalMessagesChart.js',
paths.src + 'javascripts/activityChart.js',
paths.src + 'javascripts/sidenav.js',
])
.pipe(plugins.prettyerror())
.pipe(

View File

@@ -62,4 +62,3 @@ applications:
LOGIN_DOT_GOV_CERTS_URL: ((LOGIN_DOT_GOV_CERTS_URL))
# feature flagging
FEATURE_BEST_PRACTICES_ENABLED: ((FEATURE_BEST_PRACTICES_ENABLED))

View File

@@ -2,7 +2,7 @@ from functools import partial
import pytest
from bs4 import BeautifulSoup
from flask import current_app, url_for
from flask import url_for
from freezegun import freeze_time
from tests.conftest import SERVICE_ONE_ID, normalize_spaces
@@ -83,8 +83,6 @@ def test_hiding_pages_from_search_engines(
[
"privacy",
"pricing",
"roadmap",
"features",
"documentation",
"best_practices",
"clear_goals",
@@ -93,9 +91,7 @@ def test_hiding_pages_from_search_engines(
"write_for_action",
"multiple_languages",
"benchmark_performance",
"security",
"message_status",
"features_sms",
"how_to_pay",
"get_started",
"guidance_index",
@@ -108,54 +104,33 @@ def test_hiding_pages_from_search_engines(
def test_static_pages(client_request, mock_get_organization_by_domain, view, mocker):
mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user")
# Function to check if a view is feature-flagged and should return 404 when disabled
def is_feature_flagged(view):
feature_flagged_views = [
"best_practices",
"clear_goals",
"rules_and_regulations",
"establish_trust",
"write_for_action",
"multiple_languages",
"benchmark_performance",
"guidance_index",
]
return (
not current_app.config["FEATURE_BEST_PRACTICES_ENABLED"]
and view in feature_flagged_views
)
request = partial(client_request.get, "main.{}".format(view))
# If the guidance feature is disabled, expect a 404 for feature-flagged views
if is_feature_flagged(view):
page = request(_expected_status=404)
else:
# Check the page loads when user is signed in
page = request()
assert page.select_one("meta[name=description]")
# Assert the page loads successfully
page = request(_expected_status=200)
assert page.select_one("meta[name=description]")
# Check it still works when they dont have a recent service
with client_request.session_transaction() as session:
session["service_id"] = None
request()
# Check the behavior when no recent service is set
with client_request.session_transaction() as session:
session["service_id"] = None
request()
# Check it redirects to the login screen when they sign out
client_request.logout()
with client_request.session_transaction() as session:
session["service_id"] = None
session["user_id"] = None
request(
_expected_status=302,
_expected_redirect="/sign-in?next={}".format(
url_for("main.{}".format(view))
),
)
# Check redirection to login screen when signed out
client_request.logout()
with client_request.session_transaction() as session:
session["service_id"] = None
session["user_id"] = None
request(
_expected_status=302,
_expected_redirect="/sign-in?next={}".format(
url_for("main.{}".format(view))
),
)
def test_guidance_pages_link_to_service_pages_when_signed_in(client_request, mocker):
mocker.patch("app.notify_client.user_api_client.UserApiClient.deactivate_user")
request = partial(client_request.get, "main.edit_and_format_messages")
selector = ".list-number li a"
@@ -177,18 +152,13 @@ def test_guidance_pages_link_to_service_pages_when_signed_in(client_request, moc
with client_request.session_transaction() as session:
session["service_id"] = None
session["user_id"] = None
page = request(_expected_status=302)
assert not page.select_one(selector)
request(_expected_status=302)
@pytest.mark.parametrize(
("view", "expected_view"),
[
("information_risk_management", "security"),
("old_integration_testing", "integration_testing"),
("old_roadmap", "roadmap"),
("information_security", "using_notify"),
("old_using_notify", "using_notify"),
("delivery_and_failure", "message_status"),
("callbacks", "documentation"),
],
@@ -206,10 +176,6 @@ def test_old_static_pages_redirect(client_request, view, expected_view, mocker):
)
def test_old_using_notify_page(client_request):
client_request.get("main.using_notify", _expected_status=410)
def test_css_is_served_from_correct_path(client_request):
page = client_request.get("main.documentation") # easy static page

View File

@@ -145,10 +145,9 @@ def test_should_return_200_when_email_is_not_gov_uk(
)
assert (
"Enter a public sector email address or find out who can use Notify"
"Enter a public sector email address."
in normalize_spaces(page.select_one(".usa-error-message").text)
)
assert page.select_one(".usa-error-message a")["href"] == url_for("main.features")
@pytest.mark.parametrize(

View File

@@ -84,7 +84,7 @@ def test_should_redirect_after_email_change(
[
(
"me@example.com",
"Enter a public sector email address or find out who can use Notify",
"Enter a public sector email address.",
),
(
"not_valid",

View File

@@ -94,8 +94,6 @@ EXCLUDED_ENDPOINTS = tuple(
"email_not_received",
"error",
"establish_trust",
"features",
"features_sms",
"find_services_by_name",
"find_users_by_email",
"forgot_password",
@@ -140,7 +138,6 @@ EXCLUDED_ENDPOINTS = tuple(
"notifications_sent_by_service",
"old_guest_list",
"old_integration_testing",
"old_roadmap",
"old_service_dashboard",
"old_using_notify",
"organization_billing",
@@ -169,9 +166,7 @@ EXCLUDED_ENDPOINTS = tuple(
"resume_service",
"revalidate_email_sent",
"revoke_api_key",
"roadmap",
"rules_and_regulations",
"security",
"security_policy",
"send_files_by_email",
"send_files_by_email_contact_details",
@@ -247,7 +242,6 @@ EXCLUDED_ENDPOINTS = tuple(
"user_profile_name",
"user_profile_password",
"user_profile_preferred_timezone",
"using_notify",
"verify",
"verify_email",
"view_job",

View File

@@ -1,72 +0,0 @@
import os
import re
from playwright.sync_api import expect
from tests.end_to_end.conftest import check_axe_report
E2E_TEST_URI = os.getenv("NOTIFY_E2E_TEST_URI")
def test_best_practices_side_menu(authenticated_page):
page = authenticated_page
page.goto(f"{E2E_TEST_URI}")
page.wait_for_load_state("domcontentloaded")
check_axe_report(page)
response = page.request.get(f"{E2E_TEST_URI}/test/feature-flags")
feature_flags = response.json()
feature_best_practices_enabled = feature_flags.get("FEATURE_BEST_PRACTICES_ENABLED")
if feature_best_practices_enabled:
page.get_by_role("link", name="Best Practices").click()
expect(page).to_have_title(re.compile("Best Practice"))
page.get_by_role("link", name="Clear goals", exact=True).click()
expect(page).to_have_title(re.compile("Establish clear goals"))
page.get_by_role("link", name="Rules and regulations").click()
expect(page).to_have_title(re.compile("Rules and regulations"))
page.get_by_role("link", name="Establish trust").click()
expect(page).to_have_title(re.compile("Establish trust"))
page.get_by_role("link", name="Write for action").click()
expect(page).to_have_title(re.compile("Write texts that provoke"))
page.get_by_role("link", name="Multiple languages").click()
expect(page).to_have_title(re.compile("Text in multiple languages"))
page.get_by_role("link", name="Benchmark performance").click()
expect(page).to_have_title(re.compile("Measuring performance with"))
parent_link = page.get_by_role("link", name="Establish trust")
parent_link.hover()
submenu_item = page.get_by_role("link", name=re.compile("Get the word out"))
submenu_item.click()
expect(page).to_have_url(re.compile(r"#get-the-word-out"))
anchor_target = page.locator("#get-the-word-out")
expect(anchor_target).to_be_visible()
anchor_target.click()
def test_breadcrumbs_best_practices(authenticated_page):
page = authenticated_page
page.goto(f"{E2E_TEST_URI}")
page.wait_for_load_state("domcontentloaded")
check_axe_report(page)
response = page.request.get(f"{E2E_TEST_URI}/test/feature-flags")
feature_flags = response.json()
feature_best_practices_enabled = feature_flags.get("FEATURE_BEST_PRACTICES_ENABLED")
if feature_best_practices_enabled:
page.get_by_role("link", name="Clear goals", exact=True).click()
page.locator("ol").get_by_role("link", name="Best Practices").click()

40
urls.js
View File

@@ -8,30 +8,48 @@ const sublinks = [
{ label: 'Trial Mode', path: '/using-notify/trial-mode' },
{ label: 'Pricing', path: '/using-notify/pricing' },
{ label: 'Delivery Status', path: '/using-notify/delivery-status' },
{ label: 'Guidance', path: '/guides/using-notify/guidance' },
{ label: 'Features', path: '/features' },
{ label: 'Roadmap', path: '/features/roadmap' },
{ label: 'Security', path: '/features/security' },
{ label: 'Guidance', path: '/using-notify/guidance' },
{ label: 'Support', path: '/support' },
{ label: 'Best Practices', path: '/guides/best-practices' },
{ label: 'Clear Goals', path: '/guides/clear-goals' },
{ label: 'Best Practices', path: '/using-notify/best-practices' },
{ label: 'Clear Goals', path: '/using-notify/best-practices/clear-goals' },
{
label: 'Rules And Regulations',
path: '/guides/rules-and-regulations',
path: '/using-notify/best-practices//rules-and-regulations',
},
{ label: 'Establish Trust', path: '/guides/establish-trust' },
{ label: 'Establish Trust', path: '/using-notify/best-practices//establish-trust' },
{
label: 'Write For Action',
path: '/guides/write-for-action',
path: '/using-notify/best-practices//write-for-action',
},
{
label: 'Multiple Languages',
path: '/guides/multiple-languages',
path: '/using-notify/best-practices//multiple-languages',
},
{
label: 'Benchmark Performance',
path: '/guides/benchmark-performance',
path: '/using-notify/best-practices//benchmark-performance',
},
{
label: 'About',
path: '/about',
},
{
label: 'Why Text Messaging',
path: '/about/why-text-messaging',
},
{
label: 'Security',
path: '/about/security',
},
{
label: 'Join Notify',
path: '/join-notify',
},
{
label: 'Contact',
path: '/contact',
},
// Add more links here as needed
];