Compare commits

..

3 Commits

Author SHA1 Message Date
Kenneth Kehl
3590c87334 fix report gen 2024-11-14 08:21:19 -08:00
Kenneth Kehl
8c53c137f6 fix imports 2024-11-14 08:11:41 -08:00
Kenneth Kehl
92674bfeea add methods for asynchronous report generation 2024-11-14 07:59:15 -08:00
32 changed files with 394 additions and 806 deletions

View File

@@ -161,7 +161,7 @@
"filename": "app/config.py",
"hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc",
"is_verified": false,
"line_number": 123,
"line_number": 121,
"is_secret": false
}
],
@@ -684,5 +684,5 @@
}
]
},
"generated_at": "2024-11-21T23:08:45Z"
"generated_at": "2024-10-29T19:28:03Z"
}

View File

@@ -144,7 +144,6 @@ jobs:
inputs: requirements.txt
ignore-vulns: |
PYSEC-2024-60
PYSEC-2022-43162
- name: Run npm audit
run: make npm-audit

View File

@@ -372,9 +372,15 @@ After you have completed all setup steps, you will be unable to log in, because
will not be a user in the database to link to the login.gov account you are using. So
you will need to create that user in your database using the 'create-test-user' command.
Open a terminal pointing to the api project and then run this command.
Open two terminals pointing to the api project and then run these commands in the
respective terminals.
```poetry run flask command create-test-user --admin=True```
(Server 1)
env ALLOW_EXPIRED_API_TOKEN=1 make run-flask
(Server 2)
poetry run flask command create-admin-jwt | tail -n 1 | pbcopy
poetry run flask command create-test-user --admin=True;
Supply your name, email address, mobile number, and password when prompted. Make sure the email address
is the same one you are using in login.gov and make sure your phone number is in the format 5555555555.

View File

@@ -162,17 +162,6 @@ 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[
"FEATURE_BEST_PRACTICES_ENABLED"
]
return dict(FEATURE_BEST_PRACTICES_ENABLED=feature_best_practices_enabled)
notify_environment = os.environ["NOTIFY_ENVIRONMENT"]
application.config.from_object(configs[notify_environment])

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1 +0,0 @@
<svg id="Capa_1" enable-background="new 0 0 512 512" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg"><g><g><g><path d="m377.64 347.666c-4.473 0-8.1 3.626-8.1 8.099s3.626 8.1 8.1 8.1c4.473 0 8.099-3.626 8.099-8.1s-3.626-8.099-8.099-8.099z"/><path d="m377.716 334.615c4.149 0 7.511-3.363 7.511-7.511v-67.172c0-4.148-3.362-7.511-7.511-7.511-4.148 0-7.511 3.363-7.511 7.511v67.172c0 4.148 3.362 7.511 7.511 7.511z"/><path d="m377.64 219.085c-7.974 0-15.716 1.011-23.105 2.912v-14.937c0-59.921-37.644-111.208-90.522-131.476 2.741-6.491 4.267-13.614 4.267-21.092 0-30.047-24.445-54.492-54.492-54.492s-54.491 24.445-54.491 54.491c0 7.469 1.522 14.586 4.256 21.071-16.063 6.135-30.994 15.219-44.006 26.957-3.081 2.778-3.325 7.528-.547 10.608 2.781 3.081 7.53 3.324 10.608.546 23.118-20.854 53.014-32.339 84.18-32.339 69.324 0 125.725 56.4 125.725 125.725v20.222c-32.217 14.573-54.688 47.021-54.688 84.618 0 11.997 2.288 23.47 6.45 34.005h-203.212v-138.845c0-24.475 7.036-48.196 20.346-68.599 2.267-3.474 1.288-8.128-2.187-10.394-3.474-2.266-8.129-1.288-10.394 2.186-14.907 22.851-22.787 49.41-22.787 76.807v143.829l-28.543 37.616c-1.903 2.506-2.952 5.62-2.952 8.768v27.183c0 7.996 6.505 14.501 14.501 14.501h94.92c-.468 3.102-.719 6.274-.719 9.504 0 35.036 28.504 63.541 63.541 63.541 35.036 0 63.541-28.504 63.541-63.541 0-3.232-.263-6.402-.735-9.504h29.487c4.149 0 7.511-3.363 7.511-7.511s-3.362-7.511-7.511-7.511h-249.514v-26.486l27.713-36.521h214.585c15.307 24.506 41.72 41.39 72.142 43.548v19.459h-29.946c-4.148 0-7.511 3.363-7.511 7.511s3.362 7.511 7.511 7.511h30.467c7.996 0 14.5-6.505 14.5-14.501v-20.125c47.261-4.255 84.425-44.082 84.425-92.431 0-51.178-41.636-92.814-92.814-92.814zm-127.992-148.137c-11.454-3.02-23.471-4.636-35.86-4.636-12.217 0-24.259 1.583-35.863 4.634-2.311-5.012-3.607-10.584-3.607-16.455 0-21.764 17.706-39.469 39.469-39.469s39.469 17.706 39.469 39.469c.002 5.873-1.295 11.445-3.608 16.457zm-83.427 368.007h95.144c.616 3.074.942 6.251.942 9.504 0 26.753-21.765 48.519-48.519 48.519s-48.519-21.766-48.519-48.519c.001-3.254.335-6.429.952-9.504zm211.419-49.263c-42.895 0-77.793-34.898-77.793-77.793s34.897-77.793 77.793-77.793 77.793 34.898 77.793 77.793c-.001 42.895-34.898 77.793-77.793 77.793z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -897,18 +897,3 @@ li.linked-card:hover svg,
.usa-sidenav__item a {
display: block;
}
.icon-list {
display: flex;
width: 24px;
height: 24px;
padding: 2px 1px;
justify-content: center;
align-items: center;
margin-right: 4px;
}
.indented-paragraph {
margin-left: calc(24px + 4px);
margin-top: 4px;
}

View File

@@ -91,8 +91,6 @@ class Config(object):
getenv("FEATURE_BEST_PRACTICES_ENABLED", "false") == "true"
)
FEATURE_ABOUT_PAGE_ENABLED = getenv("FEATURE_ABOUT_PAGE_ENABLED", "false") == "true"
def _s3_credentials_from_env(bucket_prefix):
return {

View File

@@ -2,15 +2,7 @@ import os
import secrets
from urllib.parse import unquote
from flask import (
abort,
current_app,
jsonify,
redirect,
render_template,
request,
url_for,
)
from flask import abort, current_app, redirect, render_template, request, url_for
from flask_login import current_user
from app import redis_client, status_api_client
@@ -18,7 +10,6 @@ from app.formatters import apply_html_class, convert_markdown_template
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,
@@ -27,38 +18,23 @@ from app.utils.user import user_is_logged_in
from notifications_utils.url_safe_token import generate_token
# Hook to check for feature flags
# Hook to check for guidance routes
@main.before_request
def check_feature_flags():
if request.path.startswith("/guides/best-practices") and not current_app.config.get(
"FEATURE_BEST_PRACTICES_ENABLED", False
def check_guidance_feature():
current_app.logger.warning("best practices 1234")
current_app.logger.warning(current_app.config["FEATURE_BEST_PRACTICES_ENABLED"])
if (
request.path.startswith("/best-practices")
and not current_app.config["FEATURE_BEST_PRACTICES_ENABLED"]
):
abort(404)
if request.path.startswith("/about") and not current_app.config.get(
"FEATURE_ABOUT_PAGE_ENABLED", False
):
abort(404)
@main.route("/test/feature-flags")
def test_feature_flags():
return jsonify(
{
"FEATURE_BEST_PRACTICES_ENABLED": current_app.config[
"FEATURE_BEST_PRACTICES_ENABLED"
]
}
)
@main.route("/")
def index():
if current_user and current_user.is_authenticated:
return redirect(url_for("main.choose_account"))
ttl = 24 * 60 * 60
# make and store the state
state = generate_token(
str(request.remote_addr),
@@ -66,12 +42,12 @@ def index():
current_app.config["DANGEROUS_SALT"],
)
state_key = f"login-state-{unquote(state)}"
redis_client.set(state_key, state, ex=ttl)
redis_client.set(state_key, state)
# make and store the nonce
nonce = secrets.token_urlsafe()
nonce_key = f"login-nonce-{unquote(nonce)}"
redis_client.set(nonce_key, nonce, ex=ttl)
redis_client.set(nonce_key, nonce)
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
if url is not None:
@@ -229,7 +205,7 @@ def trial_mode_new():
)
@main.route("/guides/best-practices")
@main.route("/best-practices")
@user_is_logged_in
def best_practices():
return render_template(
@@ -238,7 +214,7 @@ def best_practices():
)
@main.route("/guides/best-practices/clear-goals")
@main.route("/best-practices/clear-goals")
@user_is_logged_in
def clear_goals():
return render_template(
@@ -247,7 +223,7 @@ def clear_goals():
)
@main.route("/guides/best-practices/rules-and-regulations")
@main.route("/best-practices/rules-and-regulations")
@user_is_logged_in
def rules_and_regulations():
return render_template(
@@ -256,7 +232,7 @@ def rules_and_regulations():
)
@main.route("/guides/best-practices/establish-trust")
@main.route("/best-practices/establish-trust")
@user_is_logged_in
def establish_trust():
return render_template(
@@ -265,7 +241,7 @@ def establish_trust():
)
@main.route("/guides/best-practices/write-for-action")
@main.route("/best-practices/write-for-action")
@user_is_logged_in
def write_for_action():
return render_template(
@@ -274,7 +250,7 @@ def write_for_action():
)
@main.route("/guides/best-practices/multiple-languages")
@main.route("/best-practices/multiple-languages")
@user_is_logged_in
def multiple_languages():
return render_template(
@@ -283,7 +259,7 @@ def multiple_languages():
)
@main.route("/guides/best-practices/benchmark-performance")
@main.route("/best-practices/benchmark-performance")
@user_is_logged_in
def benchmark_performance():
return render_template(
@@ -293,7 +269,6 @@ def benchmark_performance():
@main.route("/using-notify/guidance")
@main.route("/guides/using-notify/guidance")
@user_is_logged_in
def guidance_index():
return render_template(
@@ -305,30 +280,6 @@ def guidance_index():
)
@main.route("/about")
def about_notify():
return render_template(
"views/about/about.html",
navigation_links=about_notify_nav(),
)
@main.route("/about/security")
def about_security():
return render_template(
"views/about/security.html",
navigation_links=about_notify_nav(),
)
@main.route("/about/why-text-messaging")
def why_text_messaging():
return render_template(
"views/about/why-text-messaging.html",
navigation_links=about_notify_nav(),
)
@main.route("/using-notify/guidance/create-and-send-messages")
@user_is_logged_in
def create_and_send_messages():

View File

@@ -10,10 +10,12 @@ from flask import (
stream_with_context,
url_for,
)
from flask_login import current_user
from app import current_service, job_api_client, notification_api_client
from app.main import main
from app.notify_client.api_key_api_client import KEY_TYPE_TEST
from app.s3_client.s3_csv_client import delete_report, report_upload
from app.utils import (
DELIVERED_STATUSES,
FAILURE_STATUSES,
@@ -144,6 +146,36 @@ def download_notifications_csv(service_id):
file_time = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
file_time = f"{file_time} {get_user_preferred_timezone()}"
csv = generate_notifications_csv(
service_id=service_id,
job_id=None,
status=filter_args.get("status"),
page=request.args.get("page", 1),
page_size=10000,
format_for_csv=True,
template_type=filter_args.get("message_type"),
limit_days=service_data_retention_days,
)
# START asynchronous reporting block
csv_file = "".join(csv)
file_location = f"reports/{service_id}/{current_user.id}/{service_data_retention_days}/report.csv"
# TODO these are some capabilities we will probably need when
# report generation becomes asynchronous.
# old_content = report_download(file_location)
# current_app.logger.info(f"OLD CONTENT IS {old_content}")
# reports = get_downloadable_reports(current_user.id, service_id)
# TODO these are to support asynchronous report generation.
# Leaving them commented in so they get exercised.
delete_report(file_location)
report_upload(file_location, csv_file)
# END asynchronous reporting block
# TODO eventually we want to remove this, when reports become fully asynchronous
# The UI should be retrieving the report elsewhere via the download_report() method call
return Response(
stream_with_context(
generate_notifications_csv(

View File

@@ -25,8 +25,7 @@ from app.main.views.verify import activate_user
from app.models.user import User
from app.utils import hide_from_search_engines
from app.utils.login import get_id_token, is_safe_redirect_url
# from app.utils.time import is_less_than_days_ago
from app.utils.time import is_less_than_days_ago
from app.utils.user import is_gov_user
from notifications_utils.url_safe_token import generate_token
@@ -109,15 +108,11 @@ def _do_login_dot_gov(): # $ pragma: no cover
)
raise Exception(f"Could not login with login.gov {login_gov_error}")
elif code and state:
verify_key = f"login-verify_email-{unquote(state)}"
verify_path = bool(redis_client.get(verify_key))
if not verify_path:
state_key = f"login-state-{unquote(state)}"
stored_state = unquote(redis_client.get(state_key).decode("utf8"))
if state != stored_state:
current_app.logger.error(f"State Error: {state} != {stored_state}")
abort(403)
state_key = f"login-state-{unquote(state)}"
stored_state = unquote(redis_client.get(state_key).decode("utf8"))
if state != stored_state:
current_app.logger.error(f"State Error: {state} != {stored_state}")
abort(403)
# activate the user
try:
@@ -135,17 +130,12 @@ def _do_login_dot_gov(): # $ pragma: no cover
f"Retrieved user {user['id']} from db #notify-admin-1505"
)
# Temporary disabling of this until we figure out what is happening.
# # Check if the email needs to be revalidated
# is_fresh_email = is_less_than_days_ago(
# user["email_access_validated_at"], 90
# )
# if not is_fresh_email:
# # send email verify
# ttl = 24 * 60 * 60
# verify_key = f"login-verify_email-{unquote(state)}"
# redis_client.set(verify_key, state, ex=ttl)
# return verify_email(user, redirect_url)
# Check if the email needs to be revalidated
is_fresh_email = is_less_than_days_ago(
user["email_access_validated_at"], 90
)
if not is_fresh_email:
return verify_email(user, redirect_url)
usr = User.from_email_address(user["email_address"])
current_app.logger.info(f"activating user {usr.id} #notify-admin-1505")
@@ -219,19 +209,17 @@ def sign_in(): # pragma: no cover
return redirect(redirect_url)
return redirect(url_for("main.show_accounts_or_dashboard"))
ttl = 24 * 60 * 60
state = generate_token(
str(request.remote_addr),
current_app.config["SECRET_KEY"],
current_app.config["DANGEROUS_SALT"],
)
state_key = f"login-state-{unquote(state)}"
redis_client.set(state_key, state, ex=ttl)
redis_client.set(state_key, state)
nonce = secrets.token_urlsafe()
nonce_key = f"login-nonce-{unquote(nonce)}"
redis_client.set(nonce_key, nonce, ex=ttl)
redis_client.set(nonce_key, nonce)
url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
# handle unit tests

View File

@@ -27,10 +27,6 @@ def using_notify_nav():
"name": "Get started",
"link": "main.get_started",
},
{
"name": "Guides",
"link": "main.best_practices",
},
{
"name": "Trial mode",
"link": "main.trial_mode_new",
@@ -105,36 +101,3 @@ def best_practices_nav():
"link": "main.benchmark_performance",
},
]
def about_notify_nav():
return [
{
"name": "About Notify",
"link": "main.about_notify",
"sub_navigation_items": [
{
"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",
"link": "main.about_security",
},
],
},
]

View File

@@ -46,15 +46,6 @@ class HeaderNavigation(Navigation):
"roadmap",
"security",
},
"best_practices": {
"best_practices",
"clear_goals",
"rules_and_regulations",
"establish_trust",
"write_for_action",
"multiple_languages",
"benchmark_performance",
},
"using_notify": {
"get_started",
"using_notify",

View File

@@ -41,8 +41,6 @@ class InviteApiClient(NotifyAdminAPIClient):
}
data = _attach_current_user(data)
ttl = 24 * 60 * 60
# make and store the state
state = generate_token(
str(request.remote_addr),
@@ -50,12 +48,12 @@ class InviteApiClient(NotifyAdminAPIClient):
current_app.config["DANGEROUS_SALT"],
)
state_key = f"login-state-{unquote(state)}"
redis_client.set(state_key, state, ex=ttl)
redis_client.set(state_key, state)
# make and store the nonce
nonce = secrets.token_urlsafe()
nonce_key = f"login-nonce-{unquote(nonce)}"
redis_client.set(nonce_key, nonce, ex=ttl) # save the nonce to redis.
redis_client.set(nonce_key, nonce) # save the nonce to redis.
data["nonce"] = nonce # This is passed to api for the invite url.
data["state"] = state # This is passed to api for the invite url.
@@ -66,7 +64,7 @@ class InviteApiClient(NotifyAdminAPIClient):
invite_data_key = f"invitedata-{unquote(state)}"
redis_invite_data = resp["invite"]
redis_invite_data = json.dumps(redis_invite_data)
redis_client.set(invite_data_key, redis_invite_data, ex=ttl)
redis_client.set(invite_data_key, redis_invite_data)
return resp_data
@@ -99,8 +97,6 @@ class InviteApiClient(NotifyAdminAPIClient):
self.post(url=f"/service/{service_id}/invite/{invited_user_id}", data=data)
def resend_invite(self, service_id, invited_user_id):
ttl = 24 * 60 * 60
# make and store the state
state = generate_token(
str(request.remote_addr),
@@ -108,12 +104,12 @@ class InviteApiClient(NotifyAdminAPIClient):
current_app.config["DANGEROUS_SALT"],
)
state_key = f"login-state-{unquote(state)}"
redis_client.set(state_key, state, ex=ttl)
redis_client.set(state_key, state)
# make and store the nonce
nonce = secrets.token_urlsafe()
nonce_key = f"login-nonce-{unquote(nonce)}"
redis_client.set(nonce_key, nonce, ex=ttl)
redis_client.set(nonce_key, nonce)
data = {
"nonce": nonce,
@@ -126,7 +122,7 @@ class InviteApiClient(NotifyAdminAPIClient):
invite_data_key = f"invitedata-{unquote(state)}"
redis_invite_data = resp["invite"]
redis_invite_data = json.dumps(redis_invite_data)
redis_client.set(invite_data_key, redis_invite_data, ex=ttl)
redis_client.set(invite_data_key, redis_invite_data)
@cache.delete("service-{service_id}")
@cache.delete("user-{invited_user_id}")

View File

@@ -116,7 +116,7 @@ class UserApiClient(NotifyAdminAPIClient):
data["next"] = next_string
if code_type == "email":
data["email_auth_link_host"] = self.admin_url
endpoint = f"/user/{user_id}/{code_type}-code"
endpoint = "/user/{0}/{1}-code".format(user_id, code_type)
current_app.logger.warn(hilite(f"Sending verify_code {code_type} to {user_id}"))
self.post(endpoint, data=data)

View File

@@ -1,9 +1,11 @@
import os
import uuid
import boto3
from flask import current_app
from app.s3_client import (
AWS_CLIENT_CONFIG,
get_s3_contents,
get_s3_metadata,
get_s3_object,
@@ -63,3 +65,70 @@ def set_metadata_on_csv_upload(service_id, upload_id, **kwargs):
def get_csv_metadata(service_id, upload_id):
return get_s3_metadata(get_csv_upload(service_id, upload_id))
def report_upload(file_location, report_content):
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
secret_key = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
utils_s3upload(
filedata=report_content,
region=region,
bucket_name=bucket_name,
file_location=file_location,
access_key=access_key,
secret_key=secret_key,
)
current_app.logger.info(f"Succcessfully uploaded report to {file_location}")
def report_download(file_location):
current_app.logger.info(f"Downloading report from {file_location}")
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
secret_key = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
return get_s3_contents(
get_s3_object(bucket_name, file_location, access_key, secret_key, region)
)
def delete_report(file_location):
current_app.logger.info(f"Deleting report from {file_location}")
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
secret_key = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
obj = get_s3_object(bucket_name, file_location, access_key, secret_key, region)
if obj is None:
return None
return obj.delete()
def get_downloadable_reports(user_id, service_id):
prefix = f"reports/{service_id}/{user_id}/"
bucket_name = current_app.config["CSV_UPLOAD_BUCKET"]["bucket"]
access_key = current_app.config["CSV_UPLOAD_BUCKET"]["access_key_id"]
secret_key = current_app.config["CSV_UPLOAD_BUCKET"]["secret_access_key"]
region = current_app.config["CSV_UPLOAD_BUCKET"]["region"]
session = boto3.Session(
aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
region_name=region,
)
s3 = session.client(
"s3",
config=AWS_CLIENT_CONFIG,
)
response = s3.list_objects_v2(Bucket=bucket_name, Prefix=prefix)
object_keys = []
if "Contents" in response:
for obj in response["Contents"]:
object_keys.append(obj["Key"])
return object_keys

View File

@@ -66,20 +66,7 @@
<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] }}">
{{ 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 %}
<a href="{{ url_for(sub_item.link.split('#')[0]) }}#{{ sub_item.link.split('#')[1] }}">{{ sub_item.name }}</a>
</li>
{% endfor %}
</ul>

View File

@@ -22,7 +22,7 @@
<span class="folder-heading-folder">{{ folder.name }}</span>
{% endif %}
{% else %}
<a href="{{ url_for('.choose_template', service_id=service_id, template_type=template_type) }}" class="usa-link {% if loop.length > 2 %}folder-heading-folder-root-truncated{% endif %}">Templates</a>
<a href="{{ url_for('.choose_template', service_id=service_id, template_type=template_type) }}" title="Templates" class="usa-link {% if loop.length > 2 %}folder-heading-folder-root-truncated{% endif %}">Templates</a>
{% endif %}
{% if not loop.last %}{{ folder_path_separator() }}{% endif %}
{% endif %}

View File

@@ -1,43 +1,32 @@
{% 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')}
] %}
{% 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')}
] %}
{% if FEATURE_BEST_PRACTICES_ENABLED %}
{% set navigation = navigation + [{"href": url_for('main.best_practices'), "text": "Guides", "active":
header_navigation.is_selected('best_practices')}] %}
{% endif %}
{% if current_user.platform_admin %}
{% set navigation = navigation + [{"href": url_for('main.platform_admin_splash_page'), "text": "Platform admin", "active": header_navigation.is_selected('platform-admin')}] %}
{% else %}
{% set navigation = navigation + [{"href": url_for('main.user_profile'), "text": "User profile", "active": header_navigation.is_selected('user-profile')}] %}
{% endif %}
{% set navigation = navigation + [
{"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')}
] %}
{% if current_service %}
{% if current_user.has_permissions('manage_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"}
] %}
{% if current_user.platform_admin %}
{% set navigation = navigation + [{"href": url_for('main.platform_admin_splash_page'), "text": "Platform admin",
"active": header_navigation.is_selected('platform-admin')}] %}
{% else %}
{% set navigation = navigation + [{"href": url_for('main.user_profile'), "text": "User profile", "active":
header_navigation.is_selected('user-profile')}] %}
{% endif %}
{% if current_service %}
{% if current_user.has_permissions('manage_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 secondaryNavigation = [{"href": url_for('main.sign_out'), "text": "Sign out"}] %}
{% endif %}
{% endif %}
{% else %}
{% set secondaryNavigation = [{"href": url_for('main.sign_out'), "text": "Sign out"}] %}
{% endif %}
{% endif %}
<header class="usa-header usa-header--extended">
@@ -47,12 +36,12 @@ secondary_navigation.is_selected('settings')},
<div class="logo-img display-flex">
<a href="/">
<span class="usa-sr-only">Notify.gov logo</span>
<img src="{{ (asset_path | default('/static')) + 'images/notify-logo.svg' }}" alt="Notify.gov logo"
class="usa-flag-logo margin-right-1">
<img src="{{ (asset_path | default('/static')) + 'images/notify-logo.svg' }}" alt="Notify.gov logo" class="usa-flag-logo margin-right-1">
</a>
</div>
{% if navigation %}
<button type="button" class="usa-menu-btn">Menu</button>
<button type="button" class="usa-menu-btn">Menu</button>
{% endif %}
</div>
</div>
@@ -63,29 +52,29 @@ secondary_navigation.is_selected('settings')},
</button>
<ul class="usa-nav__primary usa-accordion margin-right-1">
{% for item in 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 %}
{% 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-6">
<ul class="usa-nav__secondary-links">
{% if secondaryNavigation %}
{% for item in 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 %}
{% for item in 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 %}
{% endif %}
</ul>
</div>

View File

@@ -2,7 +2,7 @@
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
<div class="ajax-block-container" role="region">
<div class="ajax-block-container" aria-labelledby='pill-selected-item' role="region">
{% if job.scheduled %}
<p class="usa-body">

View File

@@ -1,76 +0,0 @@
{% extends "base.html" %}
{% set page_title = "About Notify" %}
{% block per_page_title %}
{{page_title}}
{% endblock %}
{% block content_column_content %}
<!-- {% with title=page_title %}{% include "components/best-practices/nav_breadcrumb.html" %}{% endwith %} -->
<section class="usa-prose">
<h1>{{page_title}}</h1>
<p>Notify.gov is a text messaging service built by and for the government. We help agencies communicate more
effectively with the people they serve. With Notify.gov, federal and federally-funded programs can send customized
text messages that make it possible to:</p>
<ul>
<li>Meet people where they are</li>
<li>More effectively deliver program outcomes</li>
<li>Save administrative costs</li>
<li>Implement <a class="use-link usa-link--external"
href="https://digital.gov/resources/delivering-digital-first-public-experience/" target="_blank">21st Century
IDEA</a> and other directives</li>
</ul>
<p>Notify.gov is an easy-to-use, web-based platform. It requires no technical expertise or system integration — users
can create an account and get started within minutes. We take the security and privacy of messaging data seriously
by minimizing data retention and using modern encryption methods.</p>
<!-- add youtube video component here -->
<h2 class="padding-bottom-3">Product Highlights</h2>
{% set product_highlights = [
{
"svg_src": "#send",
"card_heading": "Send customized one-way customized messages",
"p_text": "Upload a file with recipient phone numbers and Notify.gov sends customized messages",
},
{
"svg_src": "#translate",
"card_heading": "Send in recipients preferred language",
"p_text": "Notify.gov supports more than 30 character sets to send messages in almost any language",
},
{
"svg_src": "#trending_up",
"card_heading": "See how messages perform",
"p_text": "Track how many messages youve sent and monitor delivery rates",
},
{
"svg_src": "#add",
"card_heading": "Create and manage multiple services within a single organization",
"p_text": "Set up individual workspaces for different texting services, allowing multiple teams or programs to manage
day-to-day texting operations across an organization",
},
{
"svg_src": "#people",
"card_heading": "Manage team member and permissions on each service",
"p_text": "Administrators can add users and control what they can do in Notify.gov",
}
] %}
<ul class="usa-icon-list">
{% for item in product_highlights %}
<li class="usa-icon-list__item">
<div class="usa-icon-list__content padding-left-0">
<div class="usa-icon-list__icon display-flex flex-align-start">
<svg aria-hidden="true" focusable="false" role="img" class="icon-list">
<use xlink:href="{{ asset_url('img/sprite.svg') }}{{ item.svg_src }}"></use>
</svg>
<b>{{item.card_heading}}</b>
</div>
<p class="indented-paragraph">{{item.p_text}}</p>
</div>
</li>
{% endfor %}
</ul>
<p><a href="#">See if Notify is right for you</a></p>
<p>Notify.gov is a product of the <a href="#">Public Benefits Studio</a>, a product accelerator inside
the federal government. </p>
</section>
{% endblock %}

View File

@@ -1,66 +0,0 @@
{% extends "base.html" %}
{% set page_title = "Security" %}
{% block per_page_title %}
{{page_title}}
{% endblock %}
{% block content_column_content %}
<!-- {% with title=page_title %}{% include "components/best-practices/nav_breadcrumb.html" %}{% endwith %} -->
<section class="usa-prose">
<h1>{{page_title}}</h1>
<p class="font-sans-lg text-base">Notify.gov is built for the needs of government agencies with fundamental system
security processes in place to:
</p>
<ul>
<li>protect user data</li>
<li>keep systems secure</li>
<li>manage risks around information</li>
</ul>
<p>
Notify.gov operates under a full three-year <a class="use-link usa-link--external"
href="https://digital.gov/resources/an-introduction-to-ato/" target="_blank">Authority-to-Operate (ATO)</a>. This
federal security authorization process leverages security
controls provided by National Institute of Standards and Technology (NIST).
</p>
<p>
Our infrastructure runs on <a class="use-link usa-link--external" href="https://www.cloud.gov/"
target="_blank">cloud.gov</a> and utilizes several
services through Amazon Web
Services (AWS), including <a class="use-link usa-link--external" href="https://aws.amazon.com/sns/" target="_blank">
AWS SNS </a> for sending SMS
messages.
</p>
<p>For more information about the Notify.gov infrastructure, contact us at <a
href="mailto:notify-support@gsa.gov">notify-support@gsa.gov</a>.</p>
<h2>Data</h2>
<p>
On Notify.gov, data is encrypted both in transit and at rest. To send a message, agencies upload a spreadsheet of
phone numbers and other necessary data from their existing data management system.
</p>
<p>
Notify.gov is not a system of record, so it does not have a System of Records Notice (SORN). Agencies are
responsible for managing their data outside of Notify.gov.
</p>
<h3>Data retention</h3>
<p>
Any data uploads that have recipient data are held for seven calendar days; personally identifiable information
(PII) is never stored in Notifys database.
</p>
<h2>Multi-Factor Authentication</h2>
<p>
Notify.gov uses <a class="use-link usa-link--external" href="https://login.gov/what-is-login/"
target="_blank">Login.gov</a> for enhanced security.
Login.gov is an extra layer of security created by the government that uses multi-factor authentication and stronger
passwords to protect your account.
</p>
<p>
To access Notify.gov, users will use a Login.gov account associated with their agency (.gov) email with one of the
<a class="use-link usa-link--external" href="https://login.gov/help/get-started/authentication-methods/"
target="_blank">multi-factor authentication
methods</a> offered through Login.gov.
</p>
</section>
{% endblock %}

View File

@@ -1,86 +0,0 @@
{% extends "base.html" %}
{% set page_title = "Why text messaging" %}
{% block per_page_title %}
{{page_title}}
{% endblock %}
{% block content_column_content %}
<!-- {% with title=page_title, parent_title="About", url="main.why_text_messaging" %}{% include
"components/best-practices/nav_breadcrumb.html" %}{% endwith %} -->
<section class="usa-prose">
<h1>{{page_title}}</h1>
<h2 id="reach-people-using-a-common-method">Reach people using a common method</h2>
<p>
Confusing or <a class="use-link usa-link--external"
href="https://kffhealthnews.org/news/tougher-returned-mail-policies-add-to-medicaid-enrollment-drop/"
target="_blank">unreceived notifications</a> are one of the largest barriers to people getting and keeping
benefits. The typical ways the government communicates with people often fall short. Low income households are more
likely to experience housing instability, which means paper mail, already slow, can easily be missed.
</p>
<p>
<a class="use-link usa-link--external"
href="https://www.pewresearch.org/internet/fact-sheet/mobile/?tabItem=64e32376-5a21-4b1d-8f8b-5f92406db984"
target="_blank">Pew Research shows that nearly all adults in the US have a cell phone.</a> Reliance on smartphones
for online access is especially common among Americans with lower household incomes and those with lower levels of
formal education. Of those earning less than $30,000 a year, 28% say their mobile phone is the sole method to
digitally connect.
</p>
<p>
This means that for many people who rely on government services, cell phones may be the most reliable place to meet
people where they already are.
</p>
<h2 id="improve-customer-experience">Improve customer experience</h2>
<p>
Text messages can deliver concise information and drive an audience to take action quickly. Timely reminders sent
via text message have been proven to decrease re-enrollment churn and save money for administering agencies.
</p>
<p>
Texting not only helps programs reach people using a nearly-universal communication method, it is a cost effective
way to do so. With Notify.gov <a href="#">you can get started for free</a>, allowing you to try out
texting to complement your existing communications and outreach strategies.
</p>
<h2 id="what-texting-is-best-for">What texting is best for</h2>
<p>
Agencies, like you, are already using Notify.gov to text about the following programs.
</p>
{% set card_contents = [
{
"image_src": asset_url('images/calendar.svg'),
"card_heading": "Reminders",
"p_text": "In a text bubble // Your Quality Control food phone interview is on ((date)) at ((time)). Failure to
attend may lead to closure of your benefits. Call 1-800-222-3333 with questions.",
"alt_text": "reminder text example"
},
{
"image_src": asset_url('images/alert.svg'),
"card_heading": "Alerts to take action",
"p_text": "In a text bubble // Your household's Medicaid coverage is expiring. To keep getting Medicaid, you must
complete your renewal by ((date)). You can renew online at dhs.state.gov…",
"alt_text": "alerts text example"
},
{
"image_src": asset_url('images/alarm.svg'),
"card_heading": "Important status updates",
"p_text": "In a text bubble // Your passport has been issued at the Los Angeles Passport Agency. Please come to the
desk between 1:30pm and 2:30pm today to pick up your passport…",
"alt_text": "status update text example"
},
] %}
{% for item in card_contents %}
<div class="radius-lg border-2px maxw-tablet">
<div class="grid-row grid-gap-4 padding-4 padding-y-3 flex-align-center">
<div class="grid-col flex-3">
<p><b>{{item.card_heading}}</b></p>
<p>{{item.p_text}}</p>
</div>
{% if item.image_src %}
<img src="{{item.image_src}}" alt="{{ item.alt_text }}" class="height-15" />
{% endif %}
</div>
</div>
{% endfor %}
</section>
{% endblock %}

View File

@@ -23,37 +23,37 @@ Best Practices
"svg_src": "goal",
"card_heading": "Establish clear goals",
"p_text": "Start with a singular purpose. Make explicit what you want to achieve.",
"link": "/guides/best-practices/clear-goals"
"link": "/best-practices/clear-goals"
},
{
"svg_src": "compliant",
"card_heading": "Follow rules & regulations",
"p_text": "Understand what is required when texting the public.",
"link": "/guides/best-practices/rules-and-regulations"
"link": "/best-practices/rules-and-regulations"
},
{
"svg_src": "trust",
"card_heading": "Establish trust",
"p_text": "Help your audience anticipate and welcome your texts.",
"link": "/guides/best-practices/establish-trust"
"link": "/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/best-practices/write-for-action"
"link": "/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/best-practices/multiple-languages"
"link": "/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/best-practices/benchmark-performance"
"link": "/best-practices/benchmark-performance"
}
] %}

View File

@@ -103,7 +103,6 @@ def generate_notifications_csv(**kwargs):
"Carrier Response",
"Status",
"Time",
"Carrier",
]
for header in original_column_headers:
if header.lower() != "phone number":
@@ -119,7 +118,6 @@ def generate_notifications_csv(**kwargs):
"Carrier Response",
"Status",
"Time",
"Carrier",
]
yield ",".join(fieldnames) + "\n"
@@ -142,7 +140,6 @@ def generate_notifications_csv(**kwargs):
notification["provider_response"],
notification["status"],
preferred_tz_created_at,
notification["carrier"],
]
for header in original_column_headers:
if header.lower() != "phone number":
@@ -161,7 +158,6 @@ def generate_notifications_csv(**kwargs):
notification["provider_response"],
notification["status"],
preferred_tz_created_at,
notification["carrier"],
]
yield Spreadsheet.from_rows([map(str, values)]).as_csv_data

375
package-lock.json generated
View File

@@ -12,7 +12,7 @@
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/stream": "^3.0.1",
"@uswds/uswds": "^3.10.0",
"@uswds/uswds": "^3.9.0",
"cbor-js": "0.1.0",
"d3": "^7.9.0",
"govuk_frontend_toolkit": "^9.0.1",
@@ -21,10 +21,10 @@
"hogan": "1.0.2",
"jquery": "3.7.1",
"morphdom": "^2.7.4",
"playwright": "^1.49.0",
"playwright": "^1.48.2",
"python": "^0.0.4",
"query-command-supported": "1.0.0",
"sass-embedded": "^1.81.0",
"sass-embedded": "^1.80.6",
"textarea-caret": "3.1.0",
"timeago": "1.6.7",
"vinyl-buffer": "^1.0.1",
@@ -50,7 +50,7 @@
"jest-environment-jsdom": "^29.2.2",
"jshint": "2.13.6",
"jshint-stylish": "2.2.1",
"rollup": "^4.27.3",
"rollup": "^4.24.4",
"rollup-plugin-commonjs": "10.1.0",
"rollup-plugin-node-resolve": "5.2.0"
},
@@ -2667,9 +2667,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.3.tgz",
"integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.4.tgz",
"integrity": "sha512-jfUJrFct/hTA0XDM5p/htWKoNNTbDLY0KRwEt6pyOA6k2fmk0WVwl65PdUdJZgzGEHWx+49LilkcSaumQRyNQw==",
"cpu": [
"arm"
],
@@ -2679,9 +2679,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.3.tgz",
"integrity": "sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.4.tgz",
"integrity": "sha512-j4nrEO6nHU1nZUuCfRKoCcvh7PIywQPUCBa2UsootTHvTHIoIu2BzueInGJhhvQO/2FTRdNYpf63xsgEqH9IhA==",
"cpu": [
"arm64"
],
@@ -2691,9 +2691,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.3.tgz",
"integrity": "sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.4.tgz",
"integrity": "sha512-GmU/QgGtBTeraKyldC7cDVVvAJEOr3dFLKneez/n7BvX57UdhOqDsVwzU7UOnYA7AAOt+Xb26lk79PldDHgMIQ==",
"cpu": [
"arm64"
],
@@ -2703,9 +2703,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.3.tgz",
"integrity": "sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.4.tgz",
"integrity": "sha512-N6oDBiZCBKlwYcsEPXGDE4g9RoxZLK6vT98M8111cW7VsVJFpNEqvJeIPfsCzbf0XEakPslh72X0gnlMi4Ddgg==",
"cpu": [
"x64"
],
@@ -2715,9 +2715,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.3.tgz",
"integrity": "sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.24.4.tgz",
"integrity": "sha512-py5oNShCCjCyjWXCZNrRGRpjWsF0ic8f4ieBNra5buQz0O/U6mMXCpC1LvrHuhJsNPgRt36tSYMidGzZiJF6mw==",
"cpu": [
"arm64"
],
@@ -2727,9 +2727,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.3.tgz",
"integrity": "sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.24.4.tgz",
"integrity": "sha512-L7VVVW9FCnTTp4i7KrmHeDsDvjB4++KOBENYtNYAiYl96jeBThFfhP6HVxL74v4SiZEVDH/1ILscR5U9S4ms4g==",
"cpu": [
"x64"
],
@@ -2739,9 +2739,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.3.tgz",
"integrity": "sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.4.tgz",
"integrity": "sha512-10ICosOwYChROdQoQo589N5idQIisxjaFE/PAnX2i0Zr84mY0k9zul1ArH0rnJ/fpgiqfu13TFZR5A5YJLOYZA==",
"cpu": [
"arm"
],
@@ -2751,9 +2751,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.3.tgz",
"integrity": "sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.4.tgz",
"integrity": "sha512-ySAfWs69LYC7QhRDZNKqNhz2UKN8LDfbKSMAEtoEI0jitwfAG2iZwVqGACJT+kfYvvz3/JgsLlcBP+WWoKCLcw==",
"cpu": [
"arm"
],
@@ -2763,9 +2763,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.3.tgz",
"integrity": "sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.4.tgz",
"integrity": "sha512-uHYJ0HNOI6pGEeZ/5mgm5arNVTI0nLlmrbdph+pGXpC9tFHFDQmDMOEqkmUObRfosJqpU8RliYoGz06qSdtcjg==",
"cpu": [
"arm64"
],
@@ -2775,9 +2775,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.3.tgz",
"integrity": "sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.4.tgz",
"integrity": "sha512-38yiWLemQf7aLHDgTg85fh3hW9stJ0Muk7+s6tIkSUOMmi4Xbv5pH/5Bofnsb6spIwD5FJiR+jg71f0CH5OzoA==",
"cpu": [
"arm64"
],
@@ -2787,9 +2787,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.3.tgz",
"integrity": "sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.4.tgz",
"integrity": "sha512-q73XUPnkwt9ZNF2xRS4fvneSuaHw2BXuV5rI4cw0fWYVIWIBeDZX7c7FWhFQPNTnE24172K30I+dViWRVD9TwA==",
"cpu": [
"ppc64"
],
@@ -2799,9 +2799,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.3.tgz",
"integrity": "sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.4.tgz",
"integrity": "sha512-Aie/TbmQi6UXokJqDZdmTJuZBCU3QBDA8oTKRGtd4ABi/nHgXICulfg1KI6n9/koDsiDbvHAiQO3YAUNa/7BCw==",
"cpu": [
"riscv64"
],
@@ -2811,9 +2811,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.3.tgz",
"integrity": "sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.4.tgz",
"integrity": "sha512-P8MPErVO/y8ohWSP9JY7lLQ8+YMHfTI4bAdtCi3pC2hTeqFJco2jYspzOzTUB8hwUWIIu1xwOrJE11nP+0JFAQ==",
"cpu": [
"s390x"
],
@@ -2823,9 +2823,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.3.tgz",
"integrity": "sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.4.tgz",
"integrity": "sha512-K03TljaaoPK5FOyNMZAAEmhlyO49LaE4qCsr0lYHUKyb6QacTNF9pnfPpXnFlFD3TXuFbFbz7tJ51FujUXkXYA==",
"cpu": [
"x64"
],
@@ -2835,9 +2835,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.3.tgz",
"integrity": "sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.4.tgz",
"integrity": "sha512-VJYl4xSl/wqG2D5xTYncVWW+26ICV4wubwN9Gs5NrqhJtayikwCXzPL8GDsLnaLU3WwhQ8W02IinYSFJfyo34Q==",
"cpu": [
"x64"
],
@@ -2847,9 +2847,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.3.tgz",
"integrity": "sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.4.tgz",
"integrity": "sha512-ku2GvtPwQfCqoPFIJCqZ8o7bJcj+Y54cZSr43hHca6jLwAiCbZdBUOrqE6y29QFajNAzzpIOwsckaTFmN6/8TA==",
"cpu": [
"arm64"
],
@@ -2859,9 +2859,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.3.tgz",
"integrity": "sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.4.tgz",
"integrity": "sha512-V3nCe+eTt/W6UYNr/wGvO1fLpHUrnlirlypZfKCT1fG6hWfqhPgQV/K/mRBXBpxc0eKLIF18pIOFVPh0mqHjlg==",
"cpu": [
"ia32"
],
@@ -2871,9 +2871,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.3.tgz",
"integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.4.tgz",
"integrity": "sha512-LTw1Dfd0mBIEqUVCxbvTE/LLo+9ZxVC9k99v1v4ahg9Aak6FpqOfNu5kRkeTAn0wphoC4JU7No1/rL+bBCEwhg==",
"cpu": [
"x64"
],
@@ -3485,10 +3485,11 @@
}
},
"node_modules/@uswds/uswds": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/@uswds/uswds/-/uswds-3.10.0.tgz",
"integrity": "sha512-LWFTQzp4e3kqtnD/Wsyfx9uGTkn5GEpzhscNWJMIsdWBGKtiu96QT99oRJUmcsB6HbGhR0Th0FtlK/Zzx2WghA==",
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/@uswds/uswds/-/uswds-3.9.0.tgz",
"integrity": "sha512-8THm36j7iLjrDiI1D0C6b3hHsmM/Sy5Iiz+IjE+i/gYzVUMG9XVthxAZYonhU97Q1b079n6nYwlUmDSYowJecQ==",
"dependencies": {
"object-assign": "4.1.1",
"receptor": "1.0.0",
"resolve-id-refs": "0.1.0"
},
@@ -8313,8 +8314,7 @@
"node_modules/immutable": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz",
"integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==",
"dev": true
"integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ=="
},
"node_modules/import-fresh": {
"version": "3.3.0",
@@ -12011,11 +12011,11 @@
}
},
"node_modules/playwright": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz",
"integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==",
"version": "1.48.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.2.tgz",
"integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==",
"dependencies": {
"playwright-core": "1.49.0"
"playwright-core": "1.48.2"
},
"bin": {
"playwright": "cli.js"
@@ -12028,9 +12028,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz",
"integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==",
"version": "1.48.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.2.tgz",
"integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==",
"bin": {
"playwright-core": "cli.js"
},
@@ -12951,9 +12951,9 @@
"license": "Unlicense"
},
"node_modules/rollup": {
"version": "4.27.3",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.3.tgz",
"integrity": "sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==",
"version": "4.24.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.4.tgz",
"integrity": "sha512-vGorVWIsWfX3xbcyAS+I047kFKapHYivmkaT63Smj77XwvLSJos6M1xGqZnBPFQFBRZDOcG1QnYEIxAvTr/HjA==",
"dependencies": {
"@types/estree": "1.0.6"
},
@@ -12965,24 +12965,24 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.27.3",
"@rollup/rollup-android-arm64": "4.27.3",
"@rollup/rollup-darwin-arm64": "4.27.3",
"@rollup/rollup-darwin-x64": "4.27.3",
"@rollup/rollup-freebsd-arm64": "4.27.3",
"@rollup/rollup-freebsd-x64": "4.27.3",
"@rollup/rollup-linux-arm-gnueabihf": "4.27.3",
"@rollup/rollup-linux-arm-musleabihf": "4.27.3",
"@rollup/rollup-linux-arm64-gnu": "4.27.3",
"@rollup/rollup-linux-arm64-musl": "4.27.3",
"@rollup/rollup-linux-powerpc64le-gnu": "4.27.3",
"@rollup/rollup-linux-riscv64-gnu": "4.27.3",
"@rollup/rollup-linux-s390x-gnu": "4.27.3",
"@rollup/rollup-linux-x64-gnu": "4.27.3",
"@rollup/rollup-linux-x64-musl": "4.27.3",
"@rollup/rollup-win32-arm64-msvc": "4.27.3",
"@rollup/rollup-win32-ia32-msvc": "4.27.3",
"@rollup/rollup-win32-x64-msvc": "4.27.3",
"@rollup/rollup-android-arm-eabi": "4.24.4",
"@rollup/rollup-android-arm64": "4.24.4",
"@rollup/rollup-darwin-arm64": "4.24.4",
"@rollup/rollup-darwin-x64": "4.24.4",
"@rollup/rollup-freebsd-arm64": "4.24.4",
"@rollup/rollup-freebsd-x64": "4.24.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.24.4",
"@rollup/rollup-linux-arm-musleabihf": "4.24.4",
"@rollup/rollup-linux-arm64-gnu": "4.24.4",
"@rollup/rollup-linux-arm64-musl": "4.24.4",
"@rollup/rollup-linux-powerpc64le-gnu": "4.24.4",
"@rollup/rollup-linux-riscv64-gnu": "4.24.4",
"@rollup/rollup-linux-s390x-gnu": "4.24.4",
"@rollup/rollup-linux-x64-gnu": "4.24.4",
"@rollup/rollup-linux-x64-musl": "4.24.4",
"@rollup/rollup-win32-arm64-msvc": "4.24.4",
"@rollup/rollup-win32-ia32-msvc": "4.24.4",
"@rollup/rollup-win32-x64-msvc": "4.24.4",
"fsevents": "~2.3.2"
}
},
@@ -13091,17 +13091,16 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sass-embedded": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.81.0.tgz",
"integrity": "sha512-uZQ2Faxb1oWBHpeSSzjxnhClbMb3QadN0ql0ZFNuqWOLUxwaVhrMlMhPq6TDPbbfDUjihuwrMCuy695Bgna5RA==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.80.6.tgz",
"integrity": "sha512-Og4aqBnaA3oJfIpHaLuNATAqzBRgUJDYJy2X15V59cot2wYOtiT/ciPnyuq1o7vpDEeOkHhEd+mSviSlXoETug==",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"buffer-builder": "^0.2.0",
"colorjs.io": "^0.5.0",
"immutable": "^5.0.2",
"immutable": "^4.0.0",
"rxjs": "^7.4.0",
"supports-color": "^8.1.1",
"sync-child-process": "^1.0.2",
"varint": "^6.0.0"
},
"bin": {
@@ -13111,32 +13110,32 @@
"node": ">=16.0.0"
},
"optionalDependencies": {
"sass-embedded-android-arm": "1.81.0",
"sass-embedded-android-arm64": "1.81.0",
"sass-embedded-android-ia32": "1.81.0",
"sass-embedded-android-riscv64": "1.81.0",
"sass-embedded-android-x64": "1.81.0",
"sass-embedded-darwin-arm64": "1.81.0",
"sass-embedded-darwin-x64": "1.81.0",
"sass-embedded-linux-arm": "1.81.0",
"sass-embedded-linux-arm64": "1.81.0",
"sass-embedded-linux-ia32": "1.81.0",
"sass-embedded-linux-musl-arm": "1.81.0",
"sass-embedded-linux-musl-arm64": "1.81.0",
"sass-embedded-linux-musl-ia32": "1.81.0",
"sass-embedded-linux-musl-riscv64": "1.81.0",
"sass-embedded-linux-musl-x64": "1.81.0",
"sass-embedded-linux-riscv64": "1.81.0",
"sass-embedded-linux-x64": "1.81.0",
"sass-embedded-win32-arm64": "1.81.0",
"sass-embedded-win32-ia32": "1.81.0",
"sass-embedded-win32-x64": "1.81.0"
"sass-embedded-android-arm": "1.80.6",
"sass-embedded-android-arm64": "1.80.6",
"sass-embedded-android-ia32": "1.80.6",
"sass-embedded-android-riscv64": "1.80.6",
"sass-embedded-android-x64": "1.80.6",
"sass-embedded-darwin-arm64": "1.80.6",
"sass-embedded-darwin-x64": "1.80.6",
"sass-embedded-linux-arm": "1.80.6",
"sass-embedded-linux-arm64": "1.80.6",
"sass-embedded-linux-ia32": "1.80.6",
"sass-embedded-linux-musl-arm": "1.80.6",
"sass-embedded-linux-musl-arm64": "1.80.6",
"sass-embedded-linux-musl-ia32": "1.80.6",
"sass-embedded-linux-musl-riscv64": "1.80.6",
"sass-embedded-linux-musl-x64": "1.80.6",
"sass-embedded-linux-riscv64": "1.80.6",
"sass-embedded-linux-x64": "1.80.6",
"sass-embedded-win32-arm64": "1.80.6",
"sass-embedded-win32-ia32": "1.80.6",
"sass-embedded-win32-x64": "1.80.6"
}
},
"node_modules/sass-embedded-android-arm": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.81.0.tgz",
"integrity": "sha512-NWEmIuaIEsGFNsIRa+5JpIpPJyZ32H15E85CNZqEIhhwWlk9UNw7vlOCmTH8MtabtnACwC/2NG8VyNa3nxKzUQ==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.80.6.tgz",
"integrity": "sha512-UeUKMTRsnz4/dh7IzvhjONxa4/jmVp539CHDd8VZOsqg9M3HcNJNIkUzQWbuwZ+nSlWrTuo7Tvn3XlypopCBzw==",
"cpu": [
"arm"
],
@@ -13149,9 +13148,9 @@
}
},
"node_modules/sass-embedded-android-arm64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.81.0.tgz",
"integrity": "sha512-I36P77/PKAHx6sqOmexO2iEY5kpsmQ1VxcgITZSOxPMQhdB6m4t3bTabfDuWQQmCrqqiNFtLQHeytB65bUqwiw==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.80.6.tgz",
"integrity": "sha512-4rC4ZGM/k4ENVjLXnK3JTst8e8FI9MHSol2Fl7dCdYyJ3KLnlt4qL4AEYfU8zq1tcBb7CBOSZVR+CzCKubnXdg==",
"cpu": [
"arm64"
],
@@ -13164,9 +13163,9 @@
}
},
"node_modules/sass-embedded-android-ia32": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.81.0.tgz",
"integrity": "sha512-k8V1usXw30w1GVxvrteG1RzgYJzYQ9PfL2aeOqGdroBN7zYTD9VGJXTGcxA4IeeRxmRd7szVW2mKXXS472fh8g==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.80.6.tgz",
"integrity": "sha512-Lxz2SXE2KdHnynuHF+D6flDvrd55/zaEAWUeka9MxEr6FmR66d8UBOIy5ETwCSUd//S/SE5Jl6oTnHppgD1zNA==",
"cpu": [
"ia32"
],
@@ -13179,9 +13178,9 @@
}
},
"node_modules/sass-embedded-android-riscv64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.81.0.tgz",
"integrity": "sha512-RXlanyLXEpN/DEehXgLuKPsqT//GYlsGFxKXgRiCc8hIPAueFLQXKJmLWlL3BEtHgmFdbsStIu4aZCcb1hOFlQ==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.80.6.tgz",
"integrity": "sha512-hKdxY/oOqB+JJhSoBTDM5DJO1j/xtxQgayh2cLCCUx37IQQe3SEdc3V2JFf/4mIo5peaS4cjqwwSATF+l2zaXg==",
"cpu": [
"riscv64"
],
@@ -13194,9 +13193,9 @@
}
},
"node_modules/sass-embedded-android-x64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.81.0.tgz",
"integrity": "sha512-RQG0FxGQ1DERNyUDED8+BDVaLIjI+BNg8lVcyqlLZUrWY6NhzjwYEeiN/DNZmMmHtqDucAPNDcsdVUNQqsBy2A==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.80.6.tgz",
"integrity": "sha512-Eap2Fi3kTx/rVLBsOnOp5RYPr5+lFjTZ652zR24dmYFe9/sDgasakJIOPjOvD2bRuL9z0uWEY1AXVeeOPeZKrg==",
"cpu": [
"x64"
],
@@ -13209,9 +13208,9 @@
}
},
"node_modules/sass-embedded-darwin-arm64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.81.0.tgz",
"integrity": "sha512-gLKbsfII9Ppua76N41ODFnKGutla9qv0OGAas8gxe0jYBeAQFi/1iKQYdNtQtKi4mA9n5TQTqz+HHCKszZCoyA==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.80.6.tgz",
"integrity": "sha512-0mnAx8Vq6Gxj3PQt3imgITfK33hhqrSKpyHSuab71gZZni5opsdtoggq2JawW+1taRFTEZwbZJLKZ0MBDbwCCA==",
"cpu": [
"arm64"
],
@@ -13224,9 +13223,9 @@
}
},
"node_modules/sass-embedded-darwin-x64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.81.0.tgz",
"integrity": "sha512-7uMOlT9hD2KUJCbTN2XcfghDxt/rc50ujjfSjSHjX1SYj7mGplkINUXvVbbvvaV2wt6t9vkGkCo5qNbeBhfwBg==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.80.6.tgz",
"integrity": "sha512-Ib20yNZFOrJ7YVT+ltoe+JQNKPcRclM3iLAK69XZZYcSeFM/72SCoQBAaVGIpT23dxDp7FXiE4lO602c3xTRwQ==",
"cpu": [
"x64"
],
@@ -13239,9 +13238,9 @@
}
},
"node_modules/sass-embedded-linux-arm": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.81.0.tgz",
"integrity": "sha512-REqR9qM4RchCE3cKqzRy9Q4zigIV82SbSpCi/O4O3oK3pg2I1z7vkb3TiJsivusG/li7aqKZGmYOtAXjruGQDA==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.80.6.tgz",
"integrity": "sha512-QR0Q6TZox/ThuU2r9c0s3fKCgU2rXAEocpitdgxFp6tta+GsQlMFV3oON2unAa8Bwnuxkmf0YOaK0Oy/TwzkXw==",
"cpu": [
"arm"
],
@@ -13254,9 +13253,9 @@
}
},
"node_modules/sass-embedded-linux-arm64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.81.0.tgz",
"integrity": "sha512-jy4bvhdUmqbyw1jv1f3Uxl+MF8EU/Y/GDx4w6XPJm4Ds+mwH/TwnyAwsxxoBhWfnBnW8q2ADy039DlS5p+9csQ==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.80.6.tgz",
"integrity": "sha512-n5r98pBXawrQQKaxIYCMM1zDpnngsqxTkOrmvsYLFiAMCSbR0lWf/7sBB33k/Pm0D6dsbp3jpHilCoQNKI3jIw==",
"cpu": [
"arm64"
],
@@ -13269,9 +13268,9 @@
}
},
"node_modules/sass-embedded-linux-ia32": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.81.0.tgz",
"integrity": "sha512-ga/Jk4q5Bn1aC+iHJteDZuLSKnmBUiS3dEg1fnl/Z7GaHIChceKDJOw0zNaILRXI0qT2E1at9MwzoRaRA5Nn/g==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.80.6.tgz",
"integrity": "sha512-O6dWZdcOkryRdDCxVMGOeVowgblpDgVcAuRtZ1F1X7XfbpDriTQm64D+9vVZIrywYSPoJfQMJJ662cr0wUs9IQ==",
"cpu": [
"ia32"
],
@@ -13284,9 +13283,9 @@
}
},
"node_modules/sass-embedded-linux-musl-arm": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.81.0.tgz",
"integrity": "sha512-oWVUvQ4d5Kx1Md75YXZl5z1WBjc+uOhfRRqzkJ3nWc8tjszxJN+y/5EOJavhsNI3/2yoTt6eMXRTqDD9b0tWSQ==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.80.6.tgz",
"integrity": "sha512-X9FC8s8fvQGRiXc+eATlZ57N44Iq3nNa0M0ugi3ysdJwkaNYvOeS4QzBHKQAaw3QiTqdxTnLUHHVBkyzdCi9pw==",
"cpu": [
"arm"
],
@@ -13299,9 +13298,9 @@
}
},
"node_modules/sass-embedded-linux-musl-arm64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.81.0.tgz",
"integrity": "sha512-hpntWf5kjkoxncA1Vh8vhsUOquZ8AROZKx0rQh7ZjSRs4JrYZASz1cfevPKaEM3wIim/nYa6TJqm0VqWsrERlA==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.80.6.tgz",
"integrity": "sha512-VeUSHUi3MAsvOlg9QI4X/2j04h1659aE+7qKP/282CYBTrGkjFGSXZhIki9WKWDgIpDiSInRYXfQQRWhPhjCDg==",
"cpu": [
"arm64"
],
@@ -13314,9 +13313,9 @@
}
},
"node_modules/sass-embedded-linux-musl-ia32": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.81.0.tgz",
"integrity": "sha512-UEXUYkBuqTSwg5JNWiNlfMZ1Jx6SJkaEdx+fsL3Tk099L8cKSoJWH2EPz4ZJjNbyIMymrSdVfymheTeZ8u24xA==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.80.6.tgz",
"integrity": "sha512-GqitS2Nab8ah0+wfCqaxW1hnI1piC08FimL6+lM9YWK5DbCOOF82IapbvJOy0feUmd/wNnHmyNTgE9h0zVMFdQ==",
"cpu": [
"ia32"
],
@@ -13329,9 +13328,9 @@
}
},
"node_modules/sass-embedded-linux-musl-riscv64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.81.0.tgz",
"integrity": "sha512-1D7OznytbIhx2XDHWi1nuQ8d/uCVR7FGGzELgaU//T8A9DapVTUgPKvB70AF1k4GzChR9IXU/WvFZs2hDTbaJg==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.80.6.tgz",
"integrity": "sha512-ySs15z7QSRRQK/aByEEqaJLYW/sTpfynefNPZCtsVNVEzNRwy+DRpxNChtxo+QjKq97ocXETbdG5KLik7QOTJg==",
"cpu": [
"riscv64"
],
@@ -13344,9 +13343,9 @@
}
},
"node_modules/sass-embedded-linux-musl-x64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.81.0.tgz",
"integrity": "sha512-ia6VCTeVDQtBSMktXRFza1AZCt8/6aUoujot6Ugf4KmdytQqPJIHxkHaGftm5xwi9WdrMGYS7zgolToPijR11A==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.80.6.tgz",
"integrity": "sha512-DzeNqU/SN0mWFznoOH4RtVGcrg3Eoa41pUQhKMtrhNbCmIE1zNDunUiAEVTNpdHJF4nxf7ELUPXWmStM31CbUQ==",
"cpu": [
"x64"
],
@@ -13359,9 +13358,9 @@
}
},
"node_modules/sass-embedded-linux-riscv64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.81.0.tgz",
"integrity": "sha512-KbxSsqu4tT1XbhZfJV/5NfW0VtJIGlD58RjqJqJBi8Rnjrx29/upBsuwoDWtsPV/LhoGwwU1XkSa9Q1ifCz4fQ==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.80.6.tgz",
"integrity": "sha512-AyoHJ3icV9xuJjq1YzJqpEj2XfiC/KBkVYTUrCELKiXP0DN1gi/BpUwZNCAgCM3CyEdMef4LQM/ztCYJxYzdyg==",
"cpu": [
"riscv64"
],
@@ -13374,9 +13373,9 @@
}
},
"node_modules/sass-embedded-linux-x64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.81.0.tgz",
"integrity": "sha512-AMDeVY2T9WAnSFkuQcsOn5c29GRs/TuqnCiblKeXfxCSKym5uKdBl/N7GnTV6OjzoxiJBbkYKdVIaS5By7Gj4g==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.80.6.tgz",
"integrity": "sha512-EohsE9CEqx0ycylnsEj/0DNPG99Tb0qAVZspiAs5xHFCJjXOFfp3cRQu0BRf+lZ1b72IhPFXymzVtojvzUHb7g==",
"cpu": [
"x64"
],
@@ -13389,9 +13388,9 @@
}
},
"node_modules/sass-embedded-win32-arm64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.81.0.tgz",
"integrity": "sha512-YOmBRYnygwWUmCoH14QbMRHjcvCJufeJBAp0m61tOJXIQh64ziwV4mjdqjS/Rx3zhTT4T+nulDUw4d3kLiMncA==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.80.6.tgz",
"integrity": "sha512-29wETQi1ykeVvpd4zMVokpQKFSOZskGJzZawuuNCdo7BHjHKIRDsqbz8YT1CewHPBshI0hfD21fenmjxYjGXPQ==",
"cpu": [
"arm64"
],
@@ -13404,9 +13403,9 @@
}
},
"node_modules/sass-embedded-win32-ia32": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.81.0.tgz",
"integrity": "sha512-HFfr/C+uLJGGTENdnssuNTmXI/xnIasUuEHEKqI+2J0FHCWT5cpz3PGAOHymPyJcZVYGUG/7gIxIx/d7t0LFYw==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.80.6.tgz",
"integrity": "sha512-1s3OpK2iTIfIL/a91QhAQnffsbuWfnsM8Lx4Fxt0f7ErnxjCV6q8MUFTV/UhcLtLyTFnPCA62DLjp2KGCjMI9A==",
"cpu": [
"ia32"
],
@@ -13419,9 +13418,9 @@
}
},
"node_modules/sass-embedded-win32-x64": {
"version": "1.81.0",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.81.0.tgz",
"integrity": "sha512-wxj52jDcIAwWcXb7ShZ7vQYKcVUkJ+04YM9l46jDY+qwHzliGuorAUyujLyKTE9heGD3gShJ3wPPC1lXzq6v9A==",
"version": "1.80.6",
"resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.80.6.tgz",
"integrity": "sha512-0pH4Zr9silHkcmLPC0ghnD3DI0vMsjA7dKvGR32/RbbjOSvHV5cDQRLiuVJAPp34dfMA7kJd1ysSchRdH0igAQ==",
"cpu": [
"x64"
],
@@ -13446,11 +13445,6 @@
"node": ">=8"
}
},
"node_modules/sass-embedded/node_modules/immutable": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.2.tgz",
"integrity": "sha512-1NU7hWZDkV7hJ4PJ9dur9gTNQ4ePNPN4k9/0YhwjzykTi/+3Q5pF93YU5QoVj8BuOnhLgaY8gs0U2pj4kSYVcw=="
},
"node_modules/sass-embedded/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -14000,25 +13994,6 @@
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
"dev": true
},
"node_modules/sync-child-process": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
"integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
"dependencies": {
"sync-message-port": "^1.0.0"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/sync-message-port": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
"integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/table": {
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz",

View File

@@ -28,7 +28,7 @@
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rollup/stream": "^3.0.1",
"@uswds/uswds": "^3.10.0",
"@uswds/uswds": "^3.9.0",
"cbor-js": "0.1.0",
"d3": "^7.9.0",
"govuk_frontend_toolkit": "^9.0.1",
@@ -37,10 +37,10 @@
"hogan": "1.0.2",
"jquery": "3.7.1",
"morphdom": "^2.7.4",
"playwright": "^1.49.0",
"playwright": "^1.48.2",
"python": "^0.0.4",
"query-command-supported": "1.0.0",
"sass-embedded": "^1.81.0",
"sass-embedded": "^1.80.6",
"textarea-caret": "3.1.0",
"timeago": "1.6.7",
"vinyl-buffer": "^1.0.1",
@@ -66,7 +66,7 @@
"jest-environment-jsdom": "^29.2.2",
"jshint": "2.13.6",
"jshint-stylish": "2.2.1",
"rollup": "^4.27.3",
"rollup": "^4.24.4",
"rollup-plugin-commonjs": "10.1.0",
"rollup-plugin-node-resolve": "5.2.0"
}

View File

@@ -17,8 +17,6 @@ EXCLUDED_ENDPOINTS = tuple(
map(
Navigation.get_endpoint_with_blueprint,
{
"about_notify",
"about_security",
"accept_invite",
"accept_org_invite",
"accessibility_statement",
@@ -219,7 +217,6 @@ EXCLUDED_ENDPOINTS = tuple(
"suspend_service",
"template_history",
"template_usage",
"test_feature_flags",
"tour_step",
"trial_mode",
"trial_mode_new",
@@ -259,7 +256,6 @@ EXCLUDED_ENDPOINTS = tuple(
"view_template_version",
"view_template_versions",
"who_its_for",
"why_text_messaging",
"write_for_action",
},
)

View File

@@ -58,7 +58,6 @@ def _get_notifications_csv(
"to": recipient,
"recipient": recipient,
"client_reference": "ref 1234",
"carrier": "AT&T Mobility",
}
for i in range(rows)
],
@@ -89,15 +88,15 @@ def get_notifications_csv_mock(
(
None,
[
"Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time,Carrier\n",
"8005555555,foo,,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern,AT&T Mobility\r\n",
"Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time\n",
"8005555555,foo,,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n",
],
),
(
"Anne Example",
[
"Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time,Carrier\n",
"8005555555,foo,Anne Example,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern,AT&T Mobility\r\n", # noqa
"Phone Number,Template,Sent by,Batch File,Carrier Response,Status,Time\n",
"8005555555,foo,Anne Example,,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n", # noqa
],
),
],
@@ -136,7 +135,6 @@ def test_generate_notifications_csv_without_job(
"Carrier Response",
"Status",
"Time",
"Carrier",
],
[
"8005555555",
@@ -146,7 +144,6 @@ def test_generate_notifications_csv_without_job(
"Did not like it",
"Delivered",
"1943-04-19 08:00:00 AM US/Eastern",
"AT&T Mobility",
],
),
(
@@ -162,7 +159,6 @@ def test_generate_notifications_csv_without_job(
"Carrier Response",
"Status",
"Time",
"Carrier",
"a",
"b",
"c",
@@ -175,7 +171,6 @@ def test_generate_notifications_csv_without_job(
"Did not like it",
"Delivered",
"1943-04-19 08:00:00 AM US/Eastern",
"AT&T Mobility",
"🐜",
"🐝",
"🦀",
@@ -194,7 +189,6 @@ def test_generate_notifications_csv_without_job(
"Carrier Response",
"Status",
"Time",
"Carrier",
"a",
"b",
"c",
@@ -207,7 +201,6 @@ def test_generate_notifications_csv_without_job(
"Did not like it",
"Delivered",
"1943-04-19 08:00:00 AM US/Eastern",
"AT&T Mobility",
"🐜,🐜",
"🐝,🐝",
"🦀",

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}/best-practices")
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}/best-practices")
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()

26
urls.js
View File

@@ -13,25 +13,13 @@ const sublinks = [
{ label: 'Roadmap', path: '/features/roadmap' },
{ label: 'Security', path: '/features/security' },
{ label: 'Support', path: '/support' },
{ label: 'Best Practices', path: '/guides/best-practices' },
{ label: 'Clear Goals', path: '/guides/best-practices/clear-goals' },
{
label: 'Rules And Regulations',
path: '/guides/best-practices/rules-and-regulations',
},
{ label: 'Establish Trust', path: '/guides/best-practices/establish-trust' },
{
label: 'Write For Action',
path: '/guides/best-practices/write-for-action',
},
{
label: 'Multiple Languages',
path: '/guides/best-practices/multiple-languages',
},
{
label: 'Benchmark Performance',
path: '/guides/best-practices/benchmark-performance',
},
{ label: 'Best Practices', path: '/best-practices' },
{ label: 'Clear Goals', path: '/best-practices/clear-goals' },
{ label: 'Rules And Regulations', path: '/best-practices/rules-and-regulations' },
{ label: 'Establish Trust', path: '/best-practices/establish-trust' },
{ label: 'Write For Action', path: '/best-practices/write-for-action' },
{ label: 'Multiple Languages', path: '/best-practices/multiple-languages' },
{ label: 'Benchmark Performance', path: '/best-practices/benchmark-performance' },
// Add more links here as needed
];