diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 53af0de2d..be0a6cc66 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -22,7 +22,7 @@ jobs:
contents: write
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- uses: jwalton/gh-find-current-pr@v1
id: findPr
@@ -79,12 +79,12 @@ jobs:
# Maps tcp port 6379 on service container to the host
- 6379:6379
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- uses: jwalton/gh-find-current-pr@v1
id: findPr
- name: Clone API
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
repository: GSA/notifications-api
path: 'notifications-api'
@@ -140,7 +140,7 @@ jobs:
runs-on: ubuntu-latest
environment: staging
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Validate NewRelic config
env:
@@ -153,7 +153,7 @@ jobs:
dependency-audits:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
@@ -166,7 +166,7 @@ jobs:
static-scan:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Run scan
run: poetry run bandit -r app/ --confidence-level medium
@@ -174,7 +174,7 @@ jobs:
dynamic-scan:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Run server
run: make run-flask &
@@ -193,7 +193,7 @@ jobs:
a11y-scan:
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Run server
run: make run-flask &
diff --git a/.github/workflows/daily_checks.yml b/.github/workflows/daily_checks.yml
index babe60f44..a5e81a137 100644
--- a/.github/workflows/daily_checks.yml
+++ b/.github/workflows/daily_checks.yml
@@ -22,7 +22,7 @@ jobs:
dependency-audits:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Create requirements.txt
run: poetry export --without-hashes --format=requirements.txt > requirements.txt
@@ -35,7 +35,7 @@ jobs:
static-scan:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Run scan
run: poetry run bandit -r app/ --confidence-level medium
@@ -43,7 +43,7 @@ jobs:
dynamic-scan:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: ./.github/actions/setup-project
- name: Run server
run: make run-flask &
diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml
index 57f11acf9..dc725f157 100644
--- a/.github/workflows/deploy-demo.yml
+++ b/.github/workflows/deploy-demo.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
environment: demo
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 2
@@ -62,7 +62,7 @@ jobs:
LOGIN_DOT_GOV_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&post_logout_redirect_uri=https://notify-demo.app.cloud.gov/sign-out"
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://notify-demo.app.cloud.gov/sign-out"
- LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=01234567890123456789012345&prompt=select_account&redirect_uri=https://notify-demo.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=abcdefghijklmnopabcdefghijklmnop"
+ LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-demo.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=STATE"
with:
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml
index 3e169cdc9..d614bf309 100644
--- a/.github/workflows/deploy-prod.yml
+++ b/.github/workflows/deploy-prod.yml
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
environment: production
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 2
@@ -62,7 +62,7 @@ jobs:
LOGIN_DOT_GOV_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&post_logout_redirect_uri=https://beta.notify.gov/sign-out"
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://beta.notify.gov/sign-out"
- LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=01234567890123456789012345&prompt=select_account&redirect_uri=https://beta.notify.gov/sign-in&response_type=code&scope=openid+email&state=abcdefghijklmnopabcdefghijklmnop"
+ LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://beta.notify.gov/sign-in&response_type=code&scope=openid+email&state=STATE"
with:
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index f620bd662..d74ba3133 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -17,7 +17,7 @@ jobs:
environment: staging
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 2
@@ -68,7 +68,7 @@ jobs:
LOGIN_DOT_GOV_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&post_logout_redirect_uri=https://notify-staging.app.cloud.gov/sign-out"
LOGIN_DOT_GOV_BASE_LOGOUT_URL: "https://secure.login.gov/openid_connect/logout?"
LOGIN_DOT_GOV_SIGNOUT_REDIRECT: "https://notify-staging.app.cloud.gov/sign-out"
- LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=01234567890123456789012345&prompt=select_account&redirect_uri=https://notify-staging.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=abcdefghijklmnopabcdefghijklmnop"
+ LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: "https://secure.login.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:notify-gov&nonce=NONCE&prompt=select_account&redirect_uri=https://notify-staging.app.cloud.gov/sign-in&response_type=code&scope=openid+email&state=STATEE"
with:
cf_username: ${{ secrets.CLOUDGOV_USERNAME }}
cf_password: ${{ secrets.CLOUDGOV_PASSWORD }}
diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml
index 616e72689..5f495ceba 100644
--- a/.github/workflows/drift.yml
+++ b/.github/workflows/drift.yml
@@ -13,7 +13,7 @@ jobs:
environment: staging
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Check for drift
uses: dflook/terraform-check@v1
@@ -31,7 +31,7 @@ jobs:
environment: demo
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: 'production'
@@ -51,7 +51,7 @@ jobs:
environment: production
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: 'production'
diff --git a/.github/workflows/terraform-demo.yml b/.github/workflows/terraform-demo.yml
index 29b8fa397..765f9e53b 100644
--- a/.github/workflows/terraform-demo.yml
+++ b/.github/workflows/terraform-demo.yml
@@ -16,7 +16,7 @@ jobs:
environment: demo
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Terraform format
id: format
diff --git a/.github/workflows/terraform-production.yml b/.github/workflows/terraform-production.yml
index afb10dcfb..1988c5d95 100644
--- a/.github/workflows/terraform-production.yml
+++ b/.github/workflows/terraform-production.yml
@@ -16,7 +16,7 @@ jobs:
environment: production
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Terraform format
id: format
diff --git a/.github/workflows/terraform-staging.yml b/.github/workflows/terraform-staging.yml
index b1ac54f8f..1f86ee033 100644
--- a/.github/workflows/terraform-staging.yml
+++ b/.github/workflows/terraform-staging.yml
@@ -16,7 +16,7 @@ jobs:
environment: staging
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Terraform format
id: format
diff --git a/Makefile b/Makefile
index aa9b404af..21e39e844 100644
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@ NVMSH := $(shell [ -f "$(HOME)/.nvm/nvm.sh" ] && echo "$(HOME)/.nvm/nvm.sh" || e
bootstrap: generate-version-file ## Set up everything to run the app
poetry self update
poetry self add poetry-dotenv-plugin
+ poetry lock --no-update
poetry install --sync --no-root
poetry run playwright install --with-deps
poetry run pre-commit install
diff --git a/app/__init__.py b/app/__init__.py
index 47a3c15e4..5292c767b 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -52,8 +52,7 @@ from app.formatters import (
format_datetime_human,
format_datetime_normal,
format_datetime_relative,
- format_datetime_short,
- format_datetime_short_america,
+ format_datetime_table,
format_day_of_week,
format_delta,
format_delta_days,
@@ -552,8 +551,7 @@ def add_template_filters(application):
format_datetime,
format_datetime_24h,
format_datetime_normal,
- format_datetime_short,
- format_datetime_short_america,
+ format_datetime_table,
valid_phone_number,
linkable_name,
format_date,
diff --git a/app/assets/sass/uswds/_legacy-styles.scss b/app/assets/sass/uswds/_legacy-styles.scss
index b96a24844..0f16e8861 100644
--- a/app/assets/sass/uswds/_legacy-styles.scss
+++ b/app/assets/sass/uswds/_legacy-styles.scss
@@ -62,6 +62,14 @@
margin: -20px units(1) 20px units(1);
}
+h2.sms-message-header {
+ margin-bottom: 0.5rem;
+}
+
+h2.recipient-list {
+ margin-bottom: 0.5rem;
+}
+
.sms-message-status-outbound {
text-align: right;
}
diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss
index 74441f092..4a909e123 100644
--- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss
+++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss
@@ -319,27 +319,15 @@ td.table-empty-message {
.dashboard {
.big-number-with-status {
- a {
- background: color("blue-60v");
- padding: units(2);
- margin-bottom: units(1);
- text-decoration: none;
- &:hover{
- background: color("blue-warm-70v");
- }
- }
- span {
- color: white;
- }
.big-number-smaller {
display: flex;
flex-direction: column;
.big-number-number {
font-size: units(5);
- line-height: units(6);
+ line-height: units(5);
}
.big-number-label {
- font-size: units(3);
+ font-size: units(2.5);
}
}
.big-number-status {
diff --git a/app/assets/sass/uswds/_uswds-theme.scss b/app/assets/sass/uswds/_uswds-theme.scss
index b588e264d..bc4b9b733 100644
--- a/app/assets/sass/uswds/_uswds-theme.scss
+++ b/app/assets/sass/uswds/_uswds-theme.scss
@@ -9,5 +9,9 @@ in the form $setting: value,
@use "uswds-core" with (
$theme-font-type-sans: "public-sans",
- $theme-show-notifications: false
+ $theme-show-notifications: false,
+ $theme-banner-max-width: "desktop-lg",
+ $theme-grid-container-max-width: "desktop-lg",
+ $theme-footer-max-width: "desktop-lg",
+ $theme-header-max-width: "desktop-lg"
);
diff --git a/app/config.py b/app/config.py
index ba89ce6bf..0a05908f5 100644
--- a/app/config.py
+++ b/app/config.py
@@ -38,11 +38,6 @@ class Config(object):
NR_MONITOR_ON = settings and settings.monitor_mode
COMMIT_HASH = getenv("COMMIT_HASH", "--------")[0:7]
- TEMPLATE_PREVIEW_API_HOST = getenv(
- "TEMPLATE_PREVIEW_API_HOST", "http://localhost:9999"
- )
- TEMPLATE_PREVIEW_API_KEY = getenv("TEMPLATE_PREVIEW_API_KEY", "my-secret-key")
-
GOVERNMENT_EMAIL_DOMAIN_NAMES = ["gov"]
# Logging
diff --git a/app/formatters.py b/app/formatters.py
index 543167d9c..2bb8854f3 100644
--- a/app/formatters.py
+++ b/app/formatters.py
@@ -22,7 +22,7 @@ from notifications_utils.recipients import InvalidPhoneError, validate_phone_num
from notifications_utils.take import Take
from app.utils.csv import get_user_preferred_timezone
-from app.utils.time import parse_naive_dt
+from app.utils.time import parse_dt, parse_naive_dt
def apply_html_class(tags, html_file):
@@ -87,37 +87,22 @@ def format_time(date):
def format_datetime_normal(date):
+ # example: February 20, 2024 at 07:00 PM US/Eastern, used for datetimes that's not within tables
return "{} at {} {}".format(
- format_date_normal(date), format_time_24h(date), get_user_preferred_timezone()
+ format_date_normal(date), format_time_12h(date), get_user_preferred_timezone()
)
-def format_datetime_short(date):
- return "{} at {} {}".format(
- format_date_short(date), format_time_24h(date), get_user_preferred_timezone()
- )
-
-
-def format_datetime_short_america(date):
- return "{} at {}".format(format_date_numeric_america(date), format_time_12h(date))
-
-
-def format_date_numeric_america(date):
- date = parse_naive_dt(date)
-
- preferred_tz = pytz.timezone(get_user_preferred_timezone())
- return (
- date.replace(tzinfo=timezone.utc).astimezone(preferred_tz).strftime("%m-%d-%Y")
- )
+def format_datetime_table(date):
+ # example: 03-18-2024 at 04:53 PM, intended for datetimes in tables
+ return "{} at {}".format(format_date_numeric(date), format_time_12h(date))
def format_time_12h(date):
- date = parse_naive_dt(date)
+ date = parse_dt(date)
preferred_tz = pytz.timezone(get_user_preferred_timezone())
- return (
- date.replace(tzinfo=timezone.utc).astimezone(preferred_tz).strftime("%I:%M %p")
- )
+ return date.astimezone(preferred_tz).strftime("%I:%M %p")
def format_datetime_relative(date):
@@ -137,7 +122,7 @@ def format_date_numeric(date):
preferred_tz = pytz.timezone(get_user_preferred_timezone())
return (
- date.replace(tzinfo=timezone.utc).astimezone(preferred_tz).strftime("%Y-%m-%d")
+ date.replace(tzinfo=timezone.utc).astimezone(preferred_tz).strftime("%m-%d-%Y")
)
@@ -186,7 +171,7 @@ def format_date(date):
def format_date_normal(date):
date = parse_naive_dt(date)
- return date.strftime("%d %B %Y").lstrip("0")
+ return date.strftime("%B %d, %Y").lstrip("0")
def format_date_short(date):
diff --git a/app/main/forms.py b/app/main/forms.py
index 70dbfa37f..13a463a51 100644
--- a/app/main/forms.py
+++ b/app/main/forms.py
@@ -605,6 +605,15 @@ class RegisterUserForm(StripWhitespaceForm):
auth_type = HiddenField("auth_type", default="sms_auth")
+class SetupUserProfileForm(StripWhitespaceForm):
+ name = GovukTextInputField(
+ "Full name", validators=[DataRequired(message="Cannot be empty")]
+ )
+ mobile_number = international_phone_number()
+ # TODO This should be replaced with a select widget when one is available.
+ preferred_timezone = HiddenField("preferred_timezone", default="US/Eastern")
+
+
class RegisterUserFromInviteForm(RegisterUserForm):
def __init__(self, invited_user):
super().__init__(
diff --git a/app/main/views/index.py b/app/main/views/index.py
index e6107ab10..c68605b2e 100644
--- a/app/main/views/index.py
+++ b/app/main/views/index.py
@@ -1,3 +1,5 @@
+import os
+
from flask import abort, redirect, render_template, request, url_for
from flask_login import current_user
@@ -8,6 +10,8 @@ from app.main.views.pricing import CURRENT_SMS_RATE
from app.main.views.sub_navigation_dictionaries import features_nav, using_notify_nav
from app.utils.user import user_is_logged_in
+login_dot_gov_url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
+
@main.route("/")
def index():
@@ -18,6 +22,7 @@ def index():
"views/signedout.html",
sms_rate=CURRENT_SMS_RATE,
counts=status_api_client.get_count_of_live_services_and_organizations(),
+ login_dot_gov_url=login_dot_gov_url,
)
diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py
index 774f2a916..0c0848e46 100644
--- a/app/main/views/jobs.py
+++ b/app/main/views/jobs.py
@@ -10,6 +10,7 @@ from flask import (
redirect,
render_template,
request,
+ session,
stream_with_context,
url_for,
)
@@ -18,7 +19,7 @@ from notifications_utils.template import EmailPreviewTemplate, SMSBodyPreviewTem
from app import (
current_service,
- format_datetime_short,
+ format_datetime_table,
notification_api_client,
service_api_client,
)
@@ -93,7 +94,7 @@ def view_job_csv(service_id, job_id):
mimetype="text/csv",
headers={
"Content-Disposition": 'inline; filename="{} - {}.csv"'.format(
- job.template["name"], format_datetime_short(job.created_at)
+ job.template["name"], format_datetime_table(job.created_at)
)
},
)
@@ -373,6 +374,13 @@ def get_job_partials(job):
job.template_type
)
+ if request.referrer is not None:
+ session["arrived_from_preview_page"] = "check" in request.referrer
+ else:
+ session["arrived_from_preview_page"] = False
+
+ arrived_from_preview_page_url = session.get("arrived_from_preview_page", False)
+
return {
"counts": counts,
"notifications": render_template(
@@ -396,6 +404,7 @@ def get_job_partials(job):
"status": render_template(
"partials/jobs/status.html",
job=job,
+ arrived_from_preview_page_url=arrived_from_preview_page_url,
),
}
diff --git a/app/main/views/register.py b/app/main/views/register.py
index 8d3809ae9..e6350c8f7 100644
--- a/app/main/views/register.py
+++ b/app/main/views/register.py
@@ -1,14 +1,26 @@
+import uuid
from datetime import datetime, timedelta
-from flask import abort, redirect, render_template, session, url_for
+from flask import (
+ abort,
+ current_app,
+ redirect,
+ render_template,
+ request,
+ session,
+ url_for,
+)
from flask_login import current_user
+from app import user_api_client
from app.main import main
from app.main.forms import (
RegisterUserForm,
RegisterUserFromInviteForm,
RegisterUserFromOrgInviteForm,
+ SetupUserProfileForm,
)
+from app.main.views import sign_in
from app.main.views.verify import activate_user
from app.models.user import InvitedOrgUser, InvitedUser, User
from app.utils import hide_from_search_engines
@@ -120,4 +132,47 @@ def _do_registration(form, send_sms=True, send_email=True, organization_id=None)
def registration_continue():
if not session.get("user_details"):
return redirect(url_for(".show_accounts_or_dashboard"))
- return render_template("views/registration-continue.html")
+ else:
+ raise Exception("Unexpected routing in registration_continue")
+
+
+@main.route("/set-up-your-profile", methods=["GET", "POST"])
+@hide_from_search_engines
+def set_up_your_profile():
+
+ form = SetupUserProfileForm()
+
+ if form.validate_on_submit():
+ # start login.gov
+ code = request.args.get("code")
+ state = request.args.get("state")
+ login_gov_error = request.args.get("error")
+ if code and state:
+ access_token = sign_in._get_access_token(code, state)
+ user_email, user_uuid = sign_in._get_user_email_and_uuid(access_token)
+ redirect_url = request.args.get("next")
+
+ elif login_gov_error:
+ current_app.logger.error(f"login.gov error: {login_gov_error}")
+ raise Exception(f"Could not login with login.gov {login_gov_error}")
+ # end login.gov
+
+ # create the user
+ # TODO we have to provide something for password until that column goes away
+ # TODO ideally we would set the user's preferred timezone here as well
+
+ user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
+ if user is None:
+ user = User.register(
+ name=form.name.data,
+ email_address=user_email,
+ mobile_number=form.mobile_number.data,
+ password=str(uuid.uuid4()),
+ auth_type="sms_auth",
+ )
+
+ # activate the user
+ user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
+ activate_user(user["id"])
+ return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url))
+ return render_template("views/set-up-your-profile.html", form=form)
diff --git a/app/main/views/send.py b/app/main/views/send.py
index 8853b7fc3..3aa5bd830 100644
--- a/app/main/views/send.py
+++ b/app/main/views/send.py
@@ -1012,8 +1012,9 @@ def send_notification(service_id, template_id):
return redirect(
url_for(
- ".view_notification",
+ ".view_job",
service_id=service_id,
+ job_id=upload_id,
from_job=upload_id,
notification_id=notifications["notifications"][0]["id"],
# used to show the final step of the tour (help=3) or not show
diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py
index 5c5cf8a45..402e61a08 100644
--- a/app/main/views/sign_in.py
+++ b/app/main/views/sign_in.py
@@ -16,10 +16,12 @@ from flask import (
url_for,
)
from flask_login import current_user
+from notifications_utils.url_safe_token import generate_token
from app import login_manager, user_api_client
from app.main import main
from app.main.forms import LoginForm
+from app.main.views.index import error
from app.main.views.verify import activate_user
from app.models.user import InvitedUser, User
from app.utils import hide_from_search_engines
@@ -77,9 +79,7 @@ def _get_user_email_and_uuid(access_token):
return user_email, user_uuid
-@main.route("/sign-in", methods=(["GET", "POST"]))
-@hide_from_search_engines
-def sign_in():
+def _do_login_dot_gov():
# start login.gov
code = request.args.get("code")
state = request.args.get("state")
@@ -90,8 +90,13 @@ def sign_in():
redirect_url = request.args.get("next")
# activate the user
- user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
- activate_user(user["id"])
+ try:
+ user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
+ activate_user(user["id"])
+ except BaseException as be: # noqa B036
+ current_app.logger.error(be)
+ error(401)
+
return redirect(url_for("main.show_accounts_or_dashboard", next=redirect_url))
elif login_gov_error:
@@ -99,6 +104,11 @@ def sign_in():
raise Exception(f"Could not login with login.gov {login_gov_error}")
# end login.gov
+
+@main.route("/sign-in", methods=(["GET", "POST"]))
+@hide_from_search_engines
+def sign_in():
+ _do_login_dot_gov()
redirect_url = request.args.get("next")
if os.getenv("NOTIFY_E2E_TEST_EMAIL"):
@@ -165,31 +175,17 @@ def sign_in():
)
other_device = current_user.logged_in_elsewhere()
- notify_env = os.getenv("NOTIFY_ENVIRONMENT")
- current_app.logger.info("should render the sign in template")
- # TODO REMOVE THIS INFO ONCE STAGING WORKS WITH LOGIN DOT GOV
- current_app.logger.info(f"NOTIFY ENV = {notify_env}")
- current_app.logger.info(
- f"LOGIN_DOT_GOV_CLIENT_ID={os.getenv('LOGIN_DOT_GOV_CLIENT_ID')}"
+ token = generate_token(
+ str(request.remote_addr),
+ current_app.config["SECRET_KEY"],
+ current_app.config["DANGEROUS_SALT"],
)
- current_app.logger.info(
- f"LOGIN_DOT_GOV_USER_INFO_URL={os.getenv('LOGIN_DOT_GOV_USER_INFO_URL')}"
- )
- current_app.logger.info(
- f"LOGIN_DOT_GOV_ACCESS_TOKEN_URL={os.getenv('LOGIN_DOT_GOV_ACCESS_TOKEN_URL')}"
- )
- current_app.logger.info(
- f"LOGIN_DOT_GOV_LOGOUT_URL={os.getenv('LOGIN_DOT_GOV_LOGOUT_URL')}"
- )
- current_app.logger.info(
- f"LOGIN_DOT_GOV_BASE_LOGOUT_URL={os.getenv('LOGIN_DOT_GOV_BASE_LOGOUT_URL')}"
- )
- current_app.logger.info(
- f"LOGIN_DOT_GOV_SIGNOUT_REDIRECT={os.getenv('LOGIN_DOT_GOV_SIGNOUT_REDIRECT')}"
- )
- initial_signin_url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
- current_app.logger.info(f"LOGIN_DOT_GOV_INITIAL_SIGNIN_URL={initial_signin_url}")
+ url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL")
+ # handle unit tests
+ if url is not None:
+ url = url.replace("NONCE", token)
+ url = url.replace("STATE", token)
return render_template(
"views/signin.html",
@@ -198,7 +194,7 @@ def sign_in():
other_device=other_device,
login_gov_enabled=True,
password_reset_url=password_reset_url,
- initial_signin_url=initial_signin_url,
+ initial_signin_url=url,
)
diff --git a/app/main/views/sign_out.py b/app/main/views/sign_out.py
index 6d03665da..5ec96e189 100644
--- a/app/main/views/sign_out.py
+++ b/app/main/views/sign_out.py
@@ -6,10 +6,6 @@ from flask_login import current_user
from app.main import main
-# ask login.gov if we really need manual logout and what's up with one hour sessions
-# ask login.gov how they recommend approaching dev environment
-# ask Tim Donaworth the same for #2
-
def _sign_out_at_login_dot_gov():
base_url = os.getenv("LOGIN_DOT_GOV_BASE_LOGOUT_URL")
diff --git a/app/main/views/verify.py b/app/main/views/verify.py
index 0201edfcc..d87c9c6dc 100644
--- a/app/main/views/verify.py
+++ b/app/main/views/verify.py
@@ -5,6 +5,7 @@ from itsdangerous import SignatureExpired
from notifications_utils.url_safe_token import check_token
from app import user_api_client
+from app.extensions import redis_client
from app.main import main
from app.main.forms import TwoFactorForm
from app.models.user import InvitedOrgUser, InvitedUser, User
@@ -64,20 +65,46 @@ def verify_email(token):
def activate_user(user_id):
user = User.from_id(user_id)
- # the user will have a new current_session_id set by the API - store it in the cookie for future requests
+
+ # This is the login.gov path
+ login_gov_invite_data = redis_client.get(f"service-invite-{user.email_address}")
+ if login_gov_invite_data:
+ login_gov_invite_data = json.loads(login_gov_invite_data.decode("utf8"))
+
+ # This is the deprecated path for organization invites where we get id from session
session["current_session_id"] = user.current_session_id
organization_id = session.get("organization_id")
+
activated_user = user.activate()
activated_user.login()
+ # TODO when login.gov is mandatory, get rid of the if clause, it is deprecated.
invited_user = InvitedUser.from_session()
if invited_user:
service_id = _add_invited_user_to_service(invited_user)
return redirect(url_for("main.service_dashboard", service_id=service_id))
+ elif login_gov_invite_data:
+ service_id = login_gov_invite_data["service_id"]
+ user.add_to_service(
+ service_id,
+ login_gov_invite_data["permissions"],
+ login_gov_invite_data["folder_permissions"],
+ login_gov_invite_data["from_user_id"],
+ )
+ return redirect(url_for("main.service_dashboard", service_id=service_id))
+
+ # TODO when login.gov is mandatory, git rid of the if clause, it is deprecated.
invited_org_user = InvitedOrgUser.from_session()
if invited_org_user:
user_api_client.add_user_to_organization(invited_org_user.organization, user_id)
+ elif redis_client.get(f"organization-invite-{user.email_address}"):
+ organization_id = redis_client.raw_get(
+ f"organization-invite-{user.email_address}"
+ )
+ user_api_client.add_user_to_organization(
+ organization_id.decode("utf8"), user_id
+ )
if organization_id:
return redirect(url_for("main.organization_dashboard", org_id=organization_id))
diff --git a/app/notify_client/user_api_client.py b/app/notify_client/user_api_client.py
index a17059e52..e85225838 100644
--- a/app/notify_client/user_api_client.py
+++ b/app/notify_client/user_api_client.py
@@ -50,8 +50,8 @@ class UserApiClient(NotifyAdminAPIClient):
"/user/get-login-gov-user",
data={"login_uuid": user_uuid, "email": email_address},
)
- if user_data is None:
- raise Exception("User not found")
+ if user_data is None or user_data.get("data") is None:
+ return None
return user_data["data"]
def get_user_by_email_or_none(self, email_address):
diff --git a/app/templates/components/components/select/README.md b/app/templates/components/components/select/README.md
new file mode 100644
index 000000000..c656980d6
--- /dev/null
+++ b/app/templates/components/components/select/README.md
@@ -0,0 +1,13 @@
+# Select
+
+## Installation
+
+See the [main README quick start guide](https://github.com/alphagov/govuk-frontend#quick-start) for how to install this component.
+
+## Guidance and Examples
+
+Find out when to use the select component in your service in the [GOV.UK Design System](https://design-system.service.gov.uk/components/select/).
+
+## Component options
+
+Use options to customize the appearance, content and behavior of a component when using a macro, for example, changing the text.
diff --git a/app/templates/components/components/select/macro-options.json b/app/templates/components/components/select/macro-options.json
new file mode 100644
index 000000000..c786613f7
--- /dev/null
+++ b/app/templates/components/components/select/macro-options.json
@@ -0,0 +1,58 @@
+[
+ {
+ "name": "id",
+ "type": "string",
+ "required": true,
+ "description": "ID for each select box."
+ },
+ {
+ "name": "name",
+ "type": "string",
+ "required": true,
+ "description": "Name property for the select."
+ },
+ {
+ "name": "items",
+ "type": "array",
+ "required": true,
+ "description": "The items within the select component."
+ },
+ {
+ "name": "value",
+ "type": "string",
+ "required": false,
+ "description": "Value for the option which should be selected. Use this as an alternative to setting the selected option on each individual item."
+ },
+ {
+ "name": "disabled",
+ "type": "boolean",
+ "required": false,
+ "description": "If true, select box will be disabled. Use the disabled option on each individual item to only disable certain options."
+ },
+ {
+ "name": "describedBy",
+ "type": "string",
+ "required": false,
+ "description": "One or more element IDs to add to the `aria-describedby` attribute, used to provide additional descriptive information for screenreader users."
+ },
+ {
+ "name": "label",
+ "type": "object",
+ "required": true,
+ "description": "The label used by the select component.",
+ "isComponent": true
+ },
+ {
+ "name": "hint",
+ "type": "object",
+ "required": false,
+ "description": "Can be used to add a hint to the select component.",
+ "isComponent": true
+ },
+ {
+ "name": "classes",
+ "type": "string",
+ "required": false,
+ "description": "Classes to add to the select."
+ }
+]
diff --git a/app/templates/components/components/select/macro.njk b/app/templates/components/components/select/macro.njk
new file mode 100644
index 000000000..9c1333ca8
--- /dev/null
+++ b/app/templates/components/components/select/macro.njk
@@ -0,0 +1,3 @@
+{% macro usaSelect(params) %}
+ {%- include "./template.njk" -%}
+{% endmacro %}
diff --git a/app/templates/components/components/select/template.njk b/app/templates/components/components/select/template.njk
new file mode 100644
index 000000000..9468ea4ee
--- /dev/null
+++ b/app/templates/components/components/select/template.njk
@@ -0,0 +1,15 @@
+{% set describedBy = params.describedBy if params.describedBy else "" %}
+
diff --git a/app/templates/components/table.html b/app/templates/components/table.html
index a76f2c6bd..dab53fc1f 100644
--- a/app/templates/components/table.html
+++ b/app/templates/components/table.html
@@ -171,8 +171,8 @@
{% endif %}
{{ notification.status|format_notification_status_as_time(
- notification.created_at|format_datetime_short_america,
- (notification.sent_at or notification.created_at)|format_datetime_short_america
+ notification.created_at|format_datetime_table,
+ (notification.sent_at or notification.created_at)|format_datetime_table
) }}
{% if displayed_on_single_line %}{% endif %}
diff --git a/app/templates/error/401.html b/app/templates/error/401.html
index 068067e92..01cad95e3 100644
--- a/app/templates/error/401.html
+++ b/app/templates/error/401.html
@@ -4,6 +4,7 @@
You’re not authorized to see this page
-
Sign in to Notify.gov and try again.
+
If you have been invited to join Notify.gov, sign in to Notify.gov using your Login.gov account and try again.
{% endblock %}
+e
diff --git a/app/templates/main_nav.html b/app/templates/main_nav.html
index 2f7306f09..4aac354d9 100644
--- a/app/templates/main_nav.html
+++ b/app/templates/main_nav.html
@@ -14,7 +14,7 @@
Sent messages
{% endif %}
{% if current_user.has_permissions('manage_service', allow_org_user=True) %}
-
Usage
+ {#
Usage #}
{% endif %}
+ {# region header #}
{% block header %}
{% if current_user.is_authenticated %}
- {% if current_user.platform_admin %}
- {% set navigation = [
- {
- "href": url_for("main.show_accounts_or_dashboard"),
- "text": "Current service",
- "active": header_navigation.is_selected('accounts-or-dashboard')
- },
- {
- "href": url_for('main.get_started'),
- "text": "Using Notify",
- "active": header_navigation.is_selected('using_notify')
- },
- {
- "href": url_for('main.features'),
- "text": "Features",
- "active": header_navigation.is_selected('features')
- },
- {
- "href": url_for('main.platform_admin_splash_page'),
- "text": "Platform admin",
- "active": header_navigation.is_selected('platform-admin')
- },
- {
- "href": url_for('main.support'),
- "text": "Contact us",
- "active": header_navigation.is_selected('support')
- }
- ] %}
- {% if current_service %}
- {% set secondaryNavigation = [
- {
- "href": url_for('main.service_settings', service_id=current_service.id),
- "text": "Settings",
- "active": secondary_navigation.is_selected('settings')
- },
- {
- "href": url_for('main.sign_out'),
- "text": "Sign out"
- }
- ] %}
- {% else %}
- {% set secondaryNavigation = [
- {
- "href": url_for('main.sign_out'),
- "text": "Sign out"
- }
- ] %}
- {% endif %}
- {% else %}
- {% set navigation = [
- {
- "href": url_for("main.show_accounts_or_dashboard"),
- "text": "Current service",
- "active": header_navigation.is_selected('accounts-or-dashboard')
- },
- {
- "href": url_for('main.get_started'),
- "text": "Using Notify",
- "active": header_navigation.is_selected('using_notify')
- },
- {
- "href": url_for('main.features'),
- "text": "Features",
- "active": header_navigation.is_selected('features')
- },
- {
- "href": url_for('main.support'),
- "text": "Contact us",
- "active": header_navigation.is_selected('support')
- },
- {
- "href": url_for('main.user_profile'),
- "text": "User profile",
- "active": header_navigation.is_selected('user-profile')
- }
- ] %}
- {% if current_service %}
- {% set secondaryNavigation = [
- {
- "href": url_for('main.service_settings', service_id=current_service.id),
- "text": "Settings",
- "active": secondary_navigation.is_selected('settings')
- },
- {
- "href": url_for('main.sign_out'),
- "text": "Sign out"
- }
- ] %}
- {% else %}
- {% set secondaryNavigation = [
- {
- "href": url_for('main.sign_out'),
- "text": "Sign out"
- }
- ] %}
- {% endif %}
- {% endif %}
- {% else %}
-
- {# {% set navigation = [
- {
- "href": url_for('main.get_started'),
- "text": "Using Notify",
- "active": header_navigation.is_selected('using_notify')
- },
- {
- "href": url_for('main.features'),
- "text": "Features",
- "active": header_navigation.is_selected('features')
- },
- {
- "href": url_for('main.support'),
- "text": "Contact us",
- "active": header_navigation.is_selected('support')
- },
- {
- "href": url_for('main.sign_in'),
- "text": "Sign in",
- "active": header_navigation.is_selected('sign-in')
- }
- ] %} #}
+ {% include 'new/components/usa_banner.html' %}
+ {% include 'new/components/header.html' %}
{% endif %}
-
- {{ usaHeader({
- "homepageUrl": url_for('main.show_accounts_or_dashboard'),
- "productName": "Notify",
- "navigation": navigation,
- "navigationClasses": "govuk-header__navigation--end",
- "secondaryNavigation": secondaryNavigation,
- "assetsPath": asset_path + "images"
- }) }}
{% endblock %}
-
{% block main %}
diff --git a/app/templates/new/components/header.html b/app/templates/new/components/header.html
new file mode 100644
index 000000000..e21963fed
--- /dev/null
+++ b/app/templates/new/components/header.html
@@ -0,0 +1,76 @@
+{# setting navigation and secondarynavigation #}
+{% 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 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 %}
+ {% 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 %}
+{# usa header #}
+
diff --git a/app/templates/new/components/usa_banner.html b/app/templates/new/components/usa_banner.html
new file mode 100644
index 000000000..532a2937a
--- /dev/null
+++ b/app/templates/new/components/usa_banner.html
@@ -0,0 +1,44 @@
+
+{# usa banner #}
+
+
+
+
+
+
+
+
+
+ Official websites use .gov
+
+ A .gov website belongs to an official government organization in the United States.
+
+
+
+
+
+
+
+ Secure .gov websites use HTTPS
+
+ A lock ( Lock Locked padlock ) or https:// means you’ve safely connected to the .gov website. Share sensitive information only on official, secure websites.
+
+
+
+
+
+
+
diff --git a/app/templates/new/templates_glossary.md b/app/templates/new/templates_glossary.md
index bb6b247d6..04f2c4151 100644
--- a/app/templates/new/templates_glossary.md
+++ b/app/templates/new/templates_glossary.md
@@ -6,8 +6,8 @@ This document serves as a glossary for the templates directory structure of the
## Directory Structure
- `/templates`
- - `base.html`: The main base template from which all other templates inherit. This template is a combination of `main_template`, `admin_template`, `withoutnav_template` and `content_template`.
- - **/layouts**: Contains base templates and shared layouts used across the site. Simply put, it defines the overall structure or skeleton of the application (less frequently revised).
+ - `base.html`: The main base template from which all other templates inherit. This template is a combination of `main_template`, `admin_template`, `withoutnav_template`, `settings_templates`, and `content_template`.
+ - **/layouts**: Contains shared layouts used across the site. Simply put, it defines the overall structure or skeleton of the application (less frequently revised).
- `withnav_template.html`: A variation of the base layout that includes a sidebar.
- `org_template.html`: A variaton of the withnav_template
- **/components**: Houses reusable UI components that can be included in multiple templates and can be tailored with different content or links depending on the context.(more frequently revised or customized)
@@ -18,11 +18,11 @@ This document serves as a glossary for the templates directory structure of the
### Best Practices
-- Use **inheritance** (`{% extends %}`) to build on base layouts.
-- Employ **components** (`{% include %}`) for reusable UI elements to keep the code DRY and facilitate easier updates.
+- Use **inheritance** `{% extends %}` to build on base layouts.
+- Employ **components** `{% include %}` for reusable UI elements to keep the code DRY and facilitate easier updates.
### Observation Notes
-- The macro-options.json files in the header and footer component act as structural guides. They aren't directly used as data passed to the usaFooter function/macro. Instead, these files outline the expected properties and provide a description of their purpose. The `usaFooter` macro component is currently only invoked in the `admin_template`, which will eventually serve as the `base.html` template. This will simplify the approach when we change the footer macros to componenets by eliminating the need to dynamically pass this data from the base.html template.
+- The macro-options.json files in the header and footer component act as structural guides. They aren't directly used as data passed to the usaFooter function/macro. Instead, these files outline the expected properties and provide a description of their purpose. The `usaFooter` macro component is currently only invoked in the `admin_template`, which will eventually serve as the `base.html` template. This will simplify the approach when we change the footer macros to componenets by eliminating the need to dynamically pass this data from the base.html template. This is also true for `usaHeader` macro component.
diff --git a/app/templates/partials/count.html b/app/templates/partials/count.html
index 17321d67f..188884cdf 100644
--- a/app/templates/partials/count.html
+++ b/app/templates/partials/count.html
@@ -1,49 +1,7 @@
{% from "components/pill.html" import pill %}
- {% if notifications_deleted %}
-
- {% for label, query_param, url, count in counts %}
- {% if query_param == 'pending' %}
-
-
-
- {% if count is number %}
- {% if currency %}
- {{ "{}{:,.2f}".format(currency, count) }}
- {% else %}
- {{ "{:,}".format(count) }}
- {% endif %}
- {% else %}
- {{ count }}
- {% endif %}
-
- {{ query_param }}
-
-
- {% else %}
-
-
-
- {% if count is number %}
- {% if currency %}
- {{ "{}{:,.2f}".format(currency, count) }}
- {% else %}
- {{ "{:,}".format(count) }}
- {% endif %}
- {% else %}
- {{ count }}
- {% endif %}
-
- {{ label }}
-
-
- {% endif %}
- {% endfor %}
-
- {% else %}
-
- {{ pill(counts, request.args.get('status', '')) }}
-
- {% endif %}
+
+ {{ pill(counts, request.args.get('status', '')) }}
+
diff --git a/app/templates/partials/jobs/status.html b/app/templates/partials/jobs/status.html
index b24051211..99b5768e8 100644
--- a/app/templates/partials/jobs/status.html
+++ b/app/templates/partials/jobs/status.html
@@ -1,13 +1,48 @@
+{% set display_message_status %}
+{% if job.template.content %}
+
+
{{ current_service.name }}: {{job.template.content}}
+{% endif %}
+{% if job.original_file_name %}
+
Recipient list
+
+{% endif %}
+
Delivery Status
+{% endset %}
+
- {% if job.scheduled_for %}
- {% if job.processing_started %}
- Sent by {{ job.created_by.name }} on {{ job.processing_started|format_datetime_short_america }}
+ {% if job.still_processing or arrived_from_preview_page_url %}
+ {% if job.scheduled_for %}
+
+
+
Your text has been scheduled
+
+ {{ job.template_name }} - {{ current_service.name }} was scheduled on {{ job.scheduled_for|format_datetime_normal }} by {{ job.created_by.name }}
+
+
+
+ {{display_message_status}}
{% else %}
- Uploaded by {{ job.created_by.name }} on {{ job.created_at|format_datetime_short_america }}
+
+
+
Your text has been sent
+
+ {{ job.template_name }} - {{ current_service.name }} was sent on {{ job.created_at|format_datetime_normal }} by {{ job.created_by.name }}
+
+
+
+ {{display_message_status}}
{% endif %}
- {% else %}
- Sent by {{ job.created_by.name }} on {{ job.created_at|format_datetime_short_america }}
{% endif %}
{% if job.status == 'sending limits exceeded'%}
diff --git a/app/templates/views/api/keys.html b/app/templates/views/api/keys.html
index 4a9bf95e5..d32baa3e0 100644
--- a/app/templates/views/api/keys.html
+++ b/app/templates/views/api/keys.html
@@ -44,7 +44,7 @@
{% endcall %}
{% if item.expiry_date %}
{% call field(align='right') %}
-
Revoked {{ item.expiry_date|format_datetime_short }}
+
Revoked {{ item.expiry_date|format_datetime_table }}
{% endcall %}
{% else %}
{% call field(align='right', status='error') %}
diff --git a/app/templates/views/check/preview.html b/app/templates/views/check/preview.html
index 7398a7fd9..393516336 100644
--- a/app/templates/views/check/preview.html
+++ b/app/templates/views/check/preview.html
@@ -21,7 +21,7 @@
{{ page_header('Preview') }}
-
Scheduled: {{ scheduled_for if scheduled_for else 'Now'}}
+
Scheduled: {{ scheduled_for |format_datetime_normal if scheduled_for else 'Now'}}
File: {{original_file_name}}
Template: {{template.name}}
From: {{ template.sender }}
@@ -42,25 +42,55 @@
{% endif %}
diff --git a/app/templates/views/dashboard/_usage.html b/app/templates/views/dashboard/_usage.html
index dc66ad0b7..0becfc9a1 100644
--- a/app/templates/views/dashboard/_usage.html
+++ b/app/templates/views/dashboard/_usage.html
@@ -7,7 +7,7 @@
- Usage
+ Sent
Remaining
diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html
index c19b837ad..0a558ad8f 100644
--- a/app/templates/views/dashboard/dashboard.html
+++ b/app/templates/views/dashboard/dashboard.html
@@ -18,10 +18,9 @@
{{ ajax_block(partials, updates_url, 'upcoming') }}
-
+
Messages sent
- In the last seven days
{{ ajax_block(partials, updates_url, 'inbox') }}
@@ -68,7 +67,7 @@
{{ notification.template.name }}
- {{ job.created_at | format_datetime_short_america }}
+ {{ job.created_at | format_datetime_table }}
{{ notification.created_by.name }}
@@ -96,36 +95,16 @@
- Usage
- Daily
- Across all services
-
-
- Daily
-
-
-
- Usage
- Remaining
-
-
-
-
- {{ global_message_limit - daily_global_messages_remaining }}
-
- {{ daily_global_messages_remaining }}
-
-
-
-
-
+ Message count
{% if current_user.has_permissions('manage_service') %}
{{ ajax_block(partials, updates_url, 'usage') }}
- See all usage
+ During the pilot period, each service has an allowance of 250,000 message parts. Once this allowance is met, the
+ application will stop delivering messages. There's no monthly charge, no setup fee, and no procurement cost.
+
+ What counts as 1 text message part?
+ See pricing .
+
{% endif %}
diff --git a/app/templates/views/jobs/job.html b/app/templates/views/jobs/job.html
index 0c334a634..ce4e94b3d 100644
--- a/app/templates/views/jobs/job.html
+++ b/app/templates/views/jobs/job.html
@@ -5,26 +5,13 @@
{% from "components/components/back-link/macro.njk" import usaBackLink %}
{% block service_page_title %}
- {{ job.original_file_name }}
+ {{ "Message status" }}
{% endblock %}
{% block maincolumn_content %}
- {{ page_header(job.original_file_name) }}
-
- {% if not job.processing_finished %}
-
- {% endif %}
- {{ partials['status']|safe }}
- {% if not job.processing_finished %}
-
- {% endif %}
-
+ {{ page_header("Message status") }}
+ {{ partials['status']|safe }}
{% if not finished %}
-
Scheduled: {{ scheduled_for if scheduled_for else 'Now'}}
+
Scheduled: {{ scheduled_for |format_datetime_normal if scheduled_for else 'Now'}}
Template: {{template.name}}
From: {{ template.sender }}
To: {{ recipient }}
diff --git a/app/templates/views/platform-admin/complaints.html b/app/templates/views/platform-admin/complaints.html
index 10cf18fef..378d3b876 100644
--- a/app/templates/views/platform-admin/complaints.html
+++ b/app/templates/views/platform-admin/complaints.html
@@ -29,7 +29,7 @@
{{ text_field(item.complaint_type) }}
- {{ text_field(item.complaint_date|format_datetime_short if item.complaint_date else None) }}
+ {{ text_field(item.complaint_date|format_datetime_table if item.complaint_date else None) }}
{% endcall %}
diff --git a/app/templates/views/set-up-your-profile.html b/app/templates/views/set-up-your-profile.html
new file mode 100644
index 000000000..8f37d3617
--- /dev/null
+++ b/app/templates/views/set-up-your-profile.html
@@ -0,0 +1,77 @@
+{% extends "withoutnav_template.html" %}
+{% from "components/page-footer.html" import page_footer %}
+{% from "components/form.html" import form_wrapper %}
+{% from "components/components/select/macro.njk" import usaSelect -%}
+
+{% block per_page_title %}
+Set up your profile
+{% endblock %}
+
+{% block maincolumn_content %}
+
+
+
+
Set up your profile
+ {% call form_wrapper(autocomplete=True) %}
+ {{ form.name(param_extensions={}) }}
+
+
+ {{form.auth_type}}
+ {{ page_footer("Save") }}
+ {% endcall %}
+
+
+
+{% endblock %}
diff --git a/app/templates/views/signedout.html b/app/templates/views/signedout.html
index 9c7699329..3289dd121 100644
--- a/app/templates/views/signedout.html
+++ b/app/templates/views/signedout.html
@@ -21,7 +21,7 @@ Notify.gov
Reach people where they are with government-powered text messages
Notify.gov is a text message service that helps federal, state, local, tribal and territorial governments more effectively communicate with the people they serve.
Currently we are only working with select pilot partners. If you are interested in using Notify.gov in the future, please contact tts-benefits-studio@gsa.gov to learn more.
diff --git a/app/templates/views/usage.html b/app/templates/views/usage.html
index 1376163e1..f964982ee 100644
--- a/app/templates/views/usage.html
+++ b/app/templates/views/usage.html
@@ -21,9 +21,6 @@
-
Daily messages across all services
-
You have sent {{ global_message_limit - daily_global_messages_remaining }} of your {{ global_message_limit }} daily messages allowance.
-
You have {{ daily_global_messages_remaining }} messages remaining.
Text message parts
You have sent
@@ -82,8 +79,8 @@
empty_message='',
field_headings=[
'By month',
- hidden_field_heading('Text messages'),
- hidden_field_heading('Cost'),
+ 'Text message parts',
+ 'Total message allowance',
],
field_headings_visible=True
) %}
@@ -109,14 +106,9 @@
{% endcall %}
{% endfor %}
{% call field(align='left') %}
- {{ big_number(
- item.sms_cost,
- currency="$",
- smallest=True
- ) }}
{% if item.sms_free_allowance_used %}
- {{ item.sms_free_allowance_used|format_thousands }} free {{ item.sms_free_count|message_count_label('sms', suffix='') }}
+ {{ item.sms_free_allowance_used|format_thousands }} total {{ item.sms_free_count|message_count_label('parts', suffix='') }}
{% endif %}
{% for sms in item.sms_breakdown %}
{{ sms.charged_units|message_count('sms') }} at
diff --git a/app/utils/time.py b/app/utils/time.py
index 9ae24b1bd..05f190f15 100644
--- a/app/utils/time.py
+++ b/app/utils/time.py
@@ -22,3 +22,8 @@ def is_less_than_days_ago(date_from_db, number_of_days):
def parse_naive_dt(dt):
return parser.parse(dt, ignoretz=True)
+
+
+def parse_dt(dt):
+ # Parse datetime without ignoring the timezone
+ return parser.parse(dt)
diff --git a/docs/login_dot_gov.md b/docs/login_dot_gov.md
index 5f985faa4..29b65d989 100644
--- a/docs/login_dot_gov.md
+++ b/docs/login_dot_gov.md
@@ -4,11 +4,11 @@ How to integrate with the login.gov sandbox: https://dashboard.int.identitysand
1. Create a team and a user over in the login.gov sandbox.
2. Create a test app:
- a. you will need to create a unique client id that looks like: urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:test_notify_gov
- b. Select OpenIdConnect and private key JWT
- c. select authentication only
- d. select MFA required + remember device 30 days only (AAL1)
- e. set redirect urls like: http://localhost:6012/sign-in
+ - you will need to create a unique client id that looks like: urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:test_notify_gov
+ - Select OpenIdConnect and private key JWT
+ - select authentication only
+ - select MFA required + remember device 30 days only (AAL1)
+ - set redirect urls like: http://localhost:6012/sign-in
3. generate a cert: openssl req -nodes -x509 -days 365 -newkey rsa:2048 -keyout private.pem -out public.crt
4. Upload the public.crt to your app in the sandbox
5. put the private.pem contents and public.crt contents in github secrets (?)
diff --git a/poetry.lock b/poetry.lock
index 15d1346b4..930c20232 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -665,13 +665,13 @@ files = [
[[package]]
name = "eventlet"
-version = "0.35.2"
+version = "0.36.1"
description = "Highly concurrent networking library"
optional = false
python-versions = ">=3.7"
files = [
- {file = "eventlet-0.35.2-py3-none-any.whl", hash = "sha256:8fc1ee60d583f1dd58d6f304bb95fd46d34865ab22f57cb99008a81d61d573db"},
- {file = "eventlet-0.35.2.tar.gz", hash = "sha256:8d1263e20b7f816a046ac60e1d272f9e5bc503f7a34d9adc789f8a85b14fa57d"},
+ {file = "eventlet-0.36.1-py3-none-any.whl", hash = "sha256:e42d0f73b718e654c223a033b8692d1a94d778a6c1deb6c3d21442746f3f727f"},
+ {file = "eventlet-0.36.1.tar.gz", hash = "sha256:d227fe76a63d9e6a6cef53beb8ad0b2dc40a5e7737c801f4b474cfae1db07bc5"},
]
[package.dependencies]
@@ -1210,7 +1210,6 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li
optional = false
python-versions = ">=3.6"
files = [
- {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"},
{file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"},
{file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"},
{file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"},
@@ -1220,7 +1219,6 @@ files = [
{file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"},
{file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"},
{file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"},
- {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"},
{file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"},
{file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"},
{file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"},
@@ -1230,7 +1228,6 @@ files = [
{file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"},
{file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"},
{file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"},
- {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"},
{file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"},
{file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"},
{file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"},
@@ -1256,8 +1253,8 @@ files = [
{file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"},
{file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"},
{file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"},
- {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"},
{file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"},
+ {file = "lxml-5.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cfbac9f6149174f76df7e08c2e28b19d74aed90cad60383ad8671d3af7d0502f"},
{file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"},
{file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"},
{file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"},
@@ -1265,7 +1262,6 @@ files = [
{file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"},
{file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"},
{file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"},
- {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"},
{file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"},
{file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"},
{file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"},
@@ -1454,50 +1450,6 @@ files = [
{file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
]
-[[package]]
-name = "moto"
-version = "4.2.14"
-description = ""
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "moto-4.2.14-py2.py3-none-any.whl", hash = "sha256:6d242dbbabe925bb385ddb6958449e5c827670b13b8e153ed63f91dbdb50372c"},
- {file = "moto-4.2.14.tar.gz", hash = "sha256:8f9263ca70b646f091edcc93e97cda864a542e6d16ed04066b1370ed217bd190"},
-]
-
-[package.dependencies]
-boto3 = ">=1.9.201"
-botocore = ">=1.12.201"
-cryptography = ">=3.3.1"
-Jinja2 = ">=2.10.1"
-python-dateutil = ">=2.1,<3.0.0"
-requests = ">=2.5"
-responses = ">=0.13.0"
-werkzeug = ">=0.5,<2.2.0 || >2.2.0,<2.2.1 || >2.2.1"
-xmltodict = "*"
-
-[package.extras]
-all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
-apigateway = ["PyYAML (>=5.1)", "ecdsa (!=0.15)", "openapi-spec-validator (>=0.5.0)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"]
-apigatewayv2 = ["PyYAML (>=5.1)"]
-appsync = ["graphql-core"]
-awslambda = ["docker (>=3.0.0)"]
-batch = ["docker (>=3.0.0)"]
-cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
-cognitoidp = ["ecdsa (!=0.15)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"]
-dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.0)"]
-dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.0)"]
-ec2 = ["sshpubkeys (>=3.1.0)"]
-glue = ["pyparsing (>=3.0.7)"]
-iotdata = ["jsondiff (>=1.1.2)"]
-proxy = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
-resourcegroupstaggingapi = ["PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"]
-s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.5.0)"]
-s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.5.0)"]
-server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
-ssm = ["PyYAML (>=5.1)"]
-xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"]
-
[[package]]
name = "msgpack"
version = "1.0.8"
@@ -1560,7 +1512,7 @@ files = [
{file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"},
{file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"},
{file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"},
- {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"},
+ {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
]
[[package]]
@@ -1576,40 +1528,40 @@ files = [
[[package]]
name = "newrelic"
-version = "9.7.1"
+version = "9.8.0"
description = "New Relic Python Agent"
optional = false
-python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
- {file = "newrelic-9.7.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:1c1d24be69d5316af7be99d6c87686d900e708bc421ca55977cb021fd29de0bd"},
- {file = "newrelic-9.7.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8dba147f13457bd22b4509bfa700ce12bfcb8294f8b9abd4c66d4e90f90cefc2"},
- {file = "newrelic-9.7.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bbbd24bd0054a978b5d6c1be7794289c760b20d44fea526e3fb1078d060a6fe7"},
- {file = "newrelic-9.7.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:7d7f79bd828ab56446b0be941a6acb0af287ad97fe4ac5052c59ad0518f5456d"},
- {file = "newrelic-9.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fed29c65100966472bef1603c479b3b60be47078810a0d1c407e5ee133e606d7"},
- {file = "newrelic-9.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d85b7d08e7fe130951de1f2225e69c321ece620da18bbc4385905c72e0aa51b"},
- {file = "newrelic-9.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:102f6e8e65e6fa5044a0d433066a46ce5b382f96335576dfa16217c1855ebc2b"},
- {file = "newrelic-9.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0798e85b738a24843da9aa0e4175b42441d9b10af6b17ee8de137cf83d5bb222"},
- {file = "newrelic-9.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28343d596de29b7b0adcbcd2b872a1657d85c2467482792d8190814faec46c80"},
- {file = "newrelic-9.7.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a41340ce1d58bcc4dda39784d244e8a42c11b150665d8bec0527ea88bf02f53"},
- {file = "newrelic-9.7.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7c62934b8ae447bda4273a2dc4c3a65b0c7dc995af611b5003e75a34faa926f2"},
- {file = "newrelic-9.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:783c560439a08715eb00c79be64cd9471ce843f07b194592d15978909a8c85ad"},
- {file = "newrelic-9.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d61c374e4d698ee36eab80e33c485054514bd6f57c25cd8e2c9f0a40f159ebc"},
- {file = "newrelic-9.7.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08a062f6b0483de744b3085e70b88ccb7599ba4f242977bf1cbb602ed4385980"},
- {file = "newrelic-9.7.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e06c7991367e848c4f8e08e7197b0a71c145a8e32c5f92158ed64b4f6d5b4a22"},
- {file = "newrelic-9.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:166e365a6334d6b591a6af91e07dd191f043fb10893474ad1b60ed0b99a78f4e"},
- {file = "newrelic-9.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feb83b708350947846fd898debb1538ab5e0458ff56627f01e2174e73c0fe079"},
- {file = "newrelic-9.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd572a79e1848b67307be25e15f0804b9e9fc30a0d669a0fad668f3678a8869"},
- {file = "newrelic-9.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ed776ced21ebf57379cb38885485460ffd7df29cca9666197876d2045849b927"},
- {file = "newrelic-9.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b4f741be5f34e17caa57c72924045776a865efd5f9deab6ebb3b7c4f1190273b"},
- {file = "newrelic-9.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:647a5f4ff3514e7e2acbbc884319499b0ae90ec4ec93e83e7f41474cf8666e0e"},
- {file = "newrelic-9.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8faecb1fce2a25201f5496ad96181933e60b4b833f99dc143a84d5d2494a46f6"},
- {file = "newrelic-9.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:56a6b322b228ee0b3afbb59d686fad0c58b6b284fc6bb3227e7746ca0d458858"},
- {file = "newrelic-9.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6361b3be951e3520ea2b138ca56783b03f8a6c85085885fcf597d1ee28c59153"},
- {file = "newrelic-9.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82076bf4e84a1378ccd1c699e8890a8f469c3ebeec110ae5c4f03cfab25cd09b"},
- {file = "newrelic-9.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53273e8fbea3df48265b15ce3a5aee8e7950036a8463e973ef949d79072b5d74"},
- {file = "newrelic-9.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:365ec3b3795f43a70895652dff4ece28c11ecf0337aabf8da762b746cfda4c2e"},
- {file = "newrelic-9.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d143adbc38df9a576a34220d18f564208ddf88f691a1aaaf7b78c7fc653f2428"},
- {file = "newrelic-9.7.1.tar.gz", hash = "sha256:5824e599b972b5931caa13f5f34eb60df4cf3c7048604d0efe34b9ad41923739"},
+ {file = "newrelic-9.8.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:bd18c4b9b1e9cf3550ab19c384ec59a31e5f7832360d9d13a3de62fae171ce17"},
+ {file = "newrelic-9.8.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:6ecea5d54187aba8d911e7aaa0e3f7e8d332619d3837c90020c6fa41f03abe04"},
+ {file = "newrelic-9.8.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:294955819d2741fa36978a287698de7128bd18c9a6e9322b96b8c71967aa1c5d"},
+ {file = "newrelic-9.8.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:6a78fa9a8938fc45c78e354818a4e9dd9be87c74df55ad38094afe1056d75488"},
+ {file = "newrelic-9.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:606e437b51bd6e41fc358d1c9895f0739bb3af7c5889180e05d56e1c2c3774a6"},
+ {file = "newrelic-9.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1627e7ddcbb2f4c1b4157261188926e3da3db77be268c7306967cebc724aa92"},
+ {file = "newrelic-9.8.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:195640b93c3d8bc38fda5b8302313a98afc1e43dec6853355d59ba1a5441d5cb"},
+ {file = "newrelic-9.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75f2fc6260b4a049afa4229c20abfcbda3f6a0add79606fe7e0566af0b56b1b6"},
+ {file = "newrelic-9.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f75e0bb749314a18e43aba54802e3753a08a446b326ebf6653f9ea2b66da63"},
+ {file = "newrelic-9.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:708dc11213cac17eaae2a0151a9c49febdbdeba0f20ca9e572b148ab77c5af97"},
+ {file = "newrelic-9.8.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8978eb4a4f43af7f778b63251d4931519023ee1f188ff62a148e6f467ba925c5"},
+ {file = "newrelic-9.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:15ab0ff9c2526c73ad3538cb2451a651dc577369c049a379abedb946a3357a52"},
+ {file = "newrelic-9.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1e188aa29c8f8a9d12388778caab36b921a4b200475056df5895f7bd95fee0"},
+ {file = "newrelic-9.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d9e8f491c88ad2cb71f3d8b3de73540a497b4d2c2f0178573fabf0faf0676a"},
+ {file = "newrelic-9.8.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b917026043fac50e687c82cd9922759d849320bfd467daffee6392b7e874875"},
+ {file = "newrelic-9.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6bcec1a613bb523278bf2356e207b882eee105f4226b06b62fc7e38e4d30189f"},
+ {file = "newrelic-9.8.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:235d51008f2dfb63c783b5980e26214d71cdd22c8b89fe8b2640228ed2403e08"},
+ {file = "newrelic-9.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bffc9617cae1e3950c6eeb990691e0526217044f5a46a6f39b99d3459fb14430"},
+ {file = "newrelic-9.8.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fab06501364befff11cb3e99426a2baba046e0c72e86b7a42c5319bd3a19d470"},
+ {file = "newrelic-9.8.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e4c0976af8c5d21bd331bff5b9ec780afcdb3a8bd8cbf1c4969d545b4fb2fa46"},
+ {file = "newrelic-9.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8968c1bbe2cb04bc0f07e56d3988dae22e535ee3ba585f6370384363f4b1dfb"},
+ {file = "newrelic-9.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bab7dbc54e08c7a20db455e9cd635cc2a0ac48f8cdcadf6b1b40c7c6a279b7a"},
+ {file = "newrelic-9.8.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3a4e0f3203fc983801b27a3f65a83323ee5108ba6f482bb3c82691d44223098a"},
+ {file = "newrelic-9.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f274ec466271f8c1ef76fdcf4cdf0a3dfe146aa696626e52bac452d432056de0"},
+ {file = "newrelic-9.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab58426f223d407354830d38adc00ca30e563cb629ba1deef20f02e8ae5a880a"},
+ {file = "newrelic-9.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50ba95cfe20a0960911f6aa2c612e5b2e35e959d9ad43766eed8a2ea8377c606"},
+ {file = "newrelic-9.8.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:eb76abc5ef093b804c39c187241d71a7a708debd386484966f85b88fb2c79a63"},
+ {file = "newrelic-9.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ff08e87f7706329a0b56996a49827135dfaa6e556c8ea11246af7085aea5d4d"},
+ {file = "newrelic-9.8.0.tar.gz", hash = "sha256:373ceaf8876019cbc8893c0d3eac979aab26a8476902e409937b34b5581510d1"},
]
[package.extras]
@@ -1825,13 +1777,13 @@ files = [
[[package]]
name = "phonenumbers"
-version = "8.13.32"
+version = "8.13.33"
description = "Python version of Google's common library for parsing, formatting, storing and validating international phone numbers."
optional = false
python-versions = "*"
files = [
- {file = "phonenumbers-8.13.32-py2.py3-none-any.whl", hash = "sha256:ff8cd0a786fad230d454f35998984c21e501723e22df3fa61e65dbeef0919ced"},
- {file = "phonenumbers-8.13.32.tar.gz", hash = "sha256:947a7e645ee6b77dfdd47d47e2ab5a240f46556bf26d101424cc0bb76c1ca200"},
+ {file = "phonenumbers-8.13.33-py2.py3-none-any.whl", hash = "sha256:f2d653268ece55a4f3752d9cda4be6f7465f298e6d028d522aedda13cf057201"},
+ {file = "phonenumbers-8.13.33.tar.gz", hash = "sha256:991f2619f0593b36b674c345af47944ec4bae526b353cf53d707e662087be63b"},
]
[[package]]
@@ -1958,13 +1910,13 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
-version = "3.6.2"
+version = "3.7.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.9"
files = [
- {file = "pre_commit-3.6.2-py2.py3-none-any.whl", hash = "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c"},
- {file = "pre_commit-3.6.2.tar.gz", hash = "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e"},
+ {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"},
+ {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"},
]
[package.dependencies]
@@ -2001,13 +1953,13 @@ files = [
[[package]]
name = "pycparser"
-version = "2.21"
+version = "2.22"
description = "C parser in Python"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+python-versions = ">=3.8"
files = [
- {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
- {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
+ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
+ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
]
[[package]]
@@ -2227,23 +2179,23 @@ certifi = "*"
[[package]]
name = "pytest"
-version = "7.4.4"
+version = "8.1.1"
description = "pytest: simple powerful testing with Python"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
- {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
+ {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"},
+ {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
-pluggy = ">=0.12,<2.0"
+pluggy = ">=1.4,<2.0"
[package.extras]
-testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "pytest-base-url"
@@ -2282,17 +2234,17 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "pytest-mock (>=3.12)"]
[[package]]
name = "pytest-mock"
-version = "3.12.0"
+version = "3.14.0"
description = "Thin-wrapper around the mock package for easier use with pytest"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"},
- {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"},
+ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
+ {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
]
[package.dependencies]
-pytest = ">=5.0"
+pytest = ">=6.2.5"
[package.extras]
dev = ["pre-commit", "pytest-asyncio", "tox"]
@@ -2392,13 +2344,13 @@ unidecode = ["Unidecode (>=1.1.1)"]
[[package]]
name = "pytz"
-version = "2023.3.post1"
+version = "2023.4"
description = "World timezone definitions, modern and historical"
optional = false
python-versions = "*"
files = [
- {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"},
- {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"},
+ {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"},
+ {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"},
]
[[package]]
@@ -2426,7 +2378,6 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
- {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@@ -2619,41 +2570,20 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "requests-mock"
-version = "1.11.0"
+version = "1.12.1"
description = "Mock out responses from the requests package"
optional = false
-python-versions = "*"
+python-versions = ">=3.5"
files = [
- {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"},
- {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"},
+ {file = "requests-mock-1.12.1.tar.gz", hash = "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401"},
+ {file = "requests_mock-1.12.1-py2.py3-none-any.whl", hash = "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563"},
]
[package.dependencies]
-requests = ">=2.3,<3"
-six = "*"
+requests = ">=2.22,<3"
[package.extras]
fixture = ["fixtures"]
-test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"]
-
-[[package]]
-name = "responses"
-version = "0.25.0"
-description = "A utility library for mocking out the `requests` Python library."
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "responses-0.25.0-py3-none-any.whl", hash = "sha256:2f0b9c2b6437db4b528619a77e5d565e4ec2a9532162ac1a131a83529db7be1a"},
- {file = "responses-0.25.0.tar.gz", hash = "sha256:01ae6a02b4f34e39bffceb0fc6786b67a25eae919c6368d05eabc8d9576c2a66"},
-]
-
-[package.dependencies]
-pyyaml = "*"
-requests = ">=2.30.0,<3.0"
-urllib3 = ">=1.25.10,<3.0"
-
-[package.extras]
-tests = ["coverage (>=6.0.0)", "flake8", "mypy", "pytest (>=7.0.0)", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "tomli", "tomli-w", "types-PyYAML", "types-requests"]
[[package]]
name = "rich"
@@ -2938,13 +2868,13 @@ files = [
[[package]]
name = "werkzeug"
-version = "3.0.1"
+version = "3.0.2"
description = "The comprehensive WSGI web application library."
optional = false
python-versions = ">=3.8"
files = [
- {file = "werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"},
- {file = "werkzeug-3.0.1.tar.gz", hash = "sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc"},
+ {file = "werkzeug-3.0.2-py3-none-any.whl", hash = "sha256:3aac3f5da756f93030740bc235d3e09449efcf65f2f55e3602e1d851b8f48795"},
+ {file = "werkzeug-3.0.2.tar.gz", hash = "sha256:e39b645a6ac92822588e7b39a692e7828724ceae0b0d702ef96701f90e70128d"},
]
[package.dependencies]
@@ -2997,18 +2927,7 @@ files = [
{file = "xlwt-1.3.0.tar.gz", hash = "sha256:c59912717a9b28f1a3c2a98fd60741014b06b043936dcecbc113eaaada156c88"},
]
-[[package]]
-name = "xmltodict"
-version = "0.13.0"
-description = "Makes working with XML feel like you are working with JSON"
-optional = false
-python-versions = ">=3.4"
-files = [
- {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"},
- {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"},
-]
-
[metadata]
lock-version = "2.0"
python-versions = "^3.12.2"
-content-hash = "3215a1c0003ab142038dc0e104f6b68715a70b698b61841993154726682190f8"
+content-hash = "fb81af6421e07a1640bd784f2e1934237a064b30705a0c02e6f06415493e05ff"
diff --git a/pyproject.toml b/pyproject.toml
index 801d671a1..40a1aac24 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
description = ""
authors = ["Your Name "]
readme = "README.md"
+package-mode = false
[tool.poetry.dependencies]
python = "^3.12.2"
@@ -32,7 +33,7 @@ pyexcel-xlsx = "==0.6.0"
openpyxl = "==3.0.10"
pyproj = "==3.6.1"
python-dotenv = "==1.0.1"
-pytz = "==2023.3.post1"
+pytz = "~=2023.4"
rtreelib = "==0.2.0"
werkzeug = "^3.0.1"
wtforms = "~=3.1"
@@ -50,13 +51,12 @@ flake8-print = "^5.0.0"
flake8-pytest-style = "^1.7.2"
isort = "^5.13.2"
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
-moto = "^4.2"
pip-audit = "*"
pre-commit = "^3.6.0"
-pytest = "^7.4.4"
+pytest = "^8.1.1"
pytest-env = "^1.1.3"
-pytest-mock = "^3.12.0"
-pytest-playwright = "^0.4.3"
+pytest-mock = "^3.14.0"
+pytest-playwright = "^0.4.4"
pytest-xdist = "^3.5.0"
radon = "^6.0.1"
requests-mock = "^1.11.0"
diff --git a/tests/app/main/test_permissions.py b/tests/app/main/test_permissions.py
index 3ad521ae2..158fffe06 100644
--- a/tests/app/main/test_permissions.py
+++ b/tests/app/main/test_permissions.py
@@ -133,7 +133,6 @@ def test_service_navigation_for_org_user(
(
"Send messages",
"Sent messages",
- "Usage",
),
200,
),
diff --git a/tests/app/main/views/accounts/test_choose_accounts.py b/tests/app/main/views/accounts/test_choose_accounts.py
index dae5c712a..3354104b2 100644
--- a/tests/app/main/views/accounts/test_choose_accounts.py
+++ b/tests/app/main/views/accounts/test_choose_accounts.py
@@ -363,7 +363,7 @@ def test_should_show_back_to_service_if_user_belongs_to_service(
):
mock_get_service.return_value = service_one
expected_page_text = (
- "Test Service Switch service " "Send messages " "Dashboard " "Usage"
+ "Test Service Switch service " "Send messages " "Dashboard"
) # TODO: set sidebar variables in common test module
page = client_request.get(
diff --git a/tests/app/main/views/test_activity.py b/tests/app/main/views/test_activity.py
index e224c2e7d..3493aaae4 100644
--- a/tests/app/main/views/test_activity.py
+++ b/tests/app/main/views/test_activity.py
@@ -40,7 +40,7 @@ from tests.conftest import (
{"message_type": "sms"},
"/sms.json",
7,
- "Text messages",
+ "Text message",
),
(
create_active_caseworking_user(),
diff --git a/tests/app/main/views/test_api_integration.py b/tests/app/main/views/test_api_integration.py
index c1ca57f22..51592b99e 100644
--- a/tests/app/main/views/test_api_integration.py
+++ b/tests/app/main/views/test_api_integration.py
@@ -6,7 +6,7 @@ from unittest.mock import call
import pytest
from flask import url_for
-from app.formatters import format_datetime_short
+from app.formatters import format_datetime_table
from tests import sample_uuid, validate_route_permission
from tests.conftest import SERVICE_ONE_ID, normalize_spaces
@@ -123,7 +123,7 @@ def test_should_show_api_keys_page(
assert rows[0] == "API keys Action"
assert (
rows[1]
- == f"another key name Revoked {format_datetime_short(date.fromtimestamp(0).isoformat())}"
+ == f"another key name Revoked {format_datetime_table(date.fromtimestamp(0).isoformat())}"
)
assert rows[2] == "some key name Revoke some key name"
diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py
index 37674e797..4c86fb328 100644
--- a/tests/app/main/views/test_dashboard.py
+++ b/tests/app/main/views/test_dashboard.py
@@ -511,18 +511,18 @@ def test_download_inbox(
)
assert response.headers["Content-Type"] == ("text/csv; " "charset=utf-8")
assert response.headers["Content-Disposition"] == (
- "inline; " 'filename="Received text messages 2016-07-01.csv"'
+ "inline; " 'filename="Received text messages 07-01-2016.csv"'
)
assert response.get_data(as_text=True) == (
"Phone number,Message,Received\r\n"
- "(202) 867-5300,message-1,2016-07-01 11:00 US/Eastern\r\n"
- "(202) 867-5300,message-2,2016-07-01 10:59 US/Eastern\r\n"
- "(202) 867-5300,message-3,2016-07-01 10:59 US/Eastern\r\n"
- "(202) 867-5302,message-4,2016-07-01 08:59 US/Eastern\r\n"
- "+33 1 12 34 56 78,message-5,2016-07-01 06:59 US/Eastern\r\n"
- "(202) 555-0104,message-6,2016-07-01 04:59 US/Eastern\r\n"
- "(202) 555-0104,message-7,2016-07-01 02:59 US/Eastern\r\n"
- "+682 12345,message-8,2016-07-01 02:59 US/Eastern\r\n"
+ "(202) 867-5300,message-1,07-01-2016 11:00 US/Eastern\r\n"
+ "(202) 867-5300,message-2,07-01-2016 10:59 US/Eastern\r\n"
+ "(202) 867-5300,message-3,07-01-2016 10:59 US/Eastern\r\n"
+ "(202) 867-5302,message-4,07-01-2016 08:59 US/Eastern\r\n"
+ "+33 1 12 34 56 78,message-5,07-01-2016 06:59 US/Eastern\r\n"
+ "(202) 555-0104,message-6,07-01-2016 04:59 US/Eastern\r\n"
+ "(202) 555-0104,message-7,07-01-2016 02:59 US/Eastern\r\n"
+ "+682 12345,message-8,07-01-2016 02:59 US/Eastern\r\n"
)
@@ -655,7 +655,7 @@ def test_should_not_show_recent_templates_on_dashboard_if_only_one_template_used
expected_count = stats[0]["count"]
assert expected_count == 50
assert normalize_spaces(page.select_one("#total-sms .big-number-smaller").text) == (
- "{} text messages sent".format(expected_count)
+ "{} text messages sent in the last seven days".format(expected_count)
)
@@ -1045,17 +1045,8 @@ def test_usage_page_monthly_breakdown(
monthly_breakdown = normalize_spaces(page.find("table").text)
assert "October" in monthly_breakdown
- assert "249,860 free text messages" in monthly_breakdown
-
assert "February" in monthly_breakdown
- assert "$16.40" in monthly_breakdown
- assert "140 free text messages" in monthly_breakdown
- assert "960 text messages at 1.65p" in monthly_breakdown
- assert "33 text messages at 1.70p" in monthly_breakdown
-
assert "March" in monthly_breakdown
- assert "$20.91" in monthly_breakdown
- assert "1,230 text messages at 1.70p" in monthly_breakdown
@pytest.mark.parametrize(
@@ -1804,15 +1795,8 @@ def test_service_dashboard_shows_usage(
usage_table = page.find("table", class_="usage-table")
- # Check if the "Usage" table exists
- assert usage_table is not None
-
- table_rows = usage_table.find_all("tbody")[0].find_all("tr")
-
- assert len(table_rows) == 1
-
- assert "500" in table_rows[0].find_all("td")[0].text
- assert "9500" in table_rows[0].find_all("td")[1].text
+ # Check if the "Usage" table doesn't exist
+ assert usage_table is None
def test_service_dashboard_shows_free_allowance(
@@ -1847,7 +1831,7 @@ def test_service_dashboard_shows_free_allowance(
usage_text = normalize_spaces(page.select_one("[data-key=usage]").text)
assert "spent on text messages" not in usage_text
- assert "Daily Usage Remaining 1,000 249,000" in usage_text
+ assert "Daily Sent Remaining 1,000 249,000" in usage_text
def test_service_dashboard_shows_batched_jobs(
diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py
index 3076fe88f..51b52ca83 100644
--- a/tests/app/main/views/test_jobs.py
+++ b/tests/app/main/views/test_jobs.py
@@ -88,7 +88,7 @@ def test_should_show_page_for_one_job(
status=status_argument,
)
- assert page.h1.text.strip() == "thisisatest.csv"
+ assert page.h1.text.strip() == "Message status"
assert " ".join(page.find("tbody").find("tr").text.split()) == (
"2021234567 template content Delivered 01-01-2016 at 06:09 AM"
)
@@ -237,11 +237,11 @@ def test_should_show_job_with_sending_limit_exceeded_status(
job_id=fake_uuid,
)
- assert normalize_spaces(page.select("main p")[1].text) == (
+ assert normalize_spaces(page.select("main p")[2].text) == (
"Notify cannot send these messages because you have reached a limit. "
"You can only send 1,000 messages per day and 250,000 messages in total."
)
- assert normalize_spaces(page.select("main p")[2].text) == (
+ assert normalize_spaces(page.select("main p")[3].text) == (
"Upload this spreadsheet again tomorrow or contact the Notify.gov team to raise the limit."
)
@@ -320,15 +320,14 @@ def test_should_show_old_job(
service_id=SERVICE_ONE_ID,
job_id=fake_uuid,
)
- assert not page.select(".pill")
assert not page.select("p.hint")
assert not page.select("a[download]")
assert page.select_one("tbody").text.strip() == expected_message
assert [
- normalize_spaces(column.text) for column in page.select("main .grid-col-3")
+ normalize_spaces(column.text) for column in page.select("main .pill .pill-item")
] == [
"1 total text messages",
- "1 pending",
+ "1 pending text message",
"0 delivered text messages",
"0 failed text messages",
]
@@ -350,7 +349,7 @@ def test_should_show_scheduled_job(
)
assert normalize_spaces(page.select("main div p")[1].text) == (
- "Sending Two week reminder today at 00:00 US/Eastern"
+ "Example template - service one was scheduled on January 02, 2016 at 12:00 AM US/Eastern by Test User"
)
assert page.select("main p a")[0]["href"] == url_for(
@@ -424,8 +423,7 @@ def test_should_show_updates_for_one_job_as_json(
assert "2021234567" in content["notifications"]
assert "Status" in content["notifications"]
assert "Delivered" in content["notifications"]
- assert "Sent by Test User on 01-01-2016 at 12:00 AM" in content["status"]
- assert "12:00" in content["notifications"]
+ assert "01-01-2016 at 12:00 AM" in content["notifications"]
@freeze_time("2016-01-01 05:00:00.000001")
@@ -466,8 +464,7 @@ def test_should_show_updates_for_scheduled_job_as_json(
assert "2021234567" in content["notifications"]
assert "Status" in content["notifications"]
assert "Delivered" in content["notifications"]
- assert "Sent by Test User on 06-01-2016 at 04:00 PM" in content["status"]
- assert "12:00" in content["notifications"]
+ assert "01-01-2016 at 12:00 AM" in content["notifications"]
@pytest.mark.parametrize(
diff --git a/tests/app/main/views/test_notifications.py b/tests/app/main/views/test_notifications.py
index 7020cd3e0..2d1ac37b4 100644
--- a/tests/app/main/views/test_notifications.py
+++ b/tests/app/main/views/test_notifications.py
@@ -267,10 +267,6 @@ def test_notification_page_doesnt_link_to_template_in_tour(
notification_id=fake_uuid,
help=3,
)
-
- assert normalize_spaces(page.select("main p:nth-of-type(1)")[0].text) == (
- expected_message
- )
assert len(page.select("main p:nth-of-type(1) a")) == 0
diff --git a/tests/app/main/views/test_performance.py b/tests/app/main/views/test_performance.py
index ef2ed06be..5ceb5288b 100644
--- a/tests/app/main/views/test_performance.py
+++ b/tests/app/main/views/test_performance.py
@@ -116,26 +116,26 @@ def test_should_render_performance_page(
""
"Messages sent since May 2023 "
"Date Emails Text messages "
- "27 February 2021 1 2 "
- "26 February 2021 1 2 "
- "25 February 2021 1 2 "
- "24 February 2021 1 2 "
- "23 February 2021 1 2 "
- "22 February 2021 1 2 "
- "21 February 2021 1,234,567 123,456 "
+ "February 27, 2021 1 2 "
+ "February 26, 2021 1 2 "
+ "February 25, 2021 1 2 "
+ "February 24, 2021 1 2 "
+ "February 23, 2021 1 2 "
+ "February 22, 2021 1 2 "
+ "February 21, 2021 1,234,567 123,456 "
"Only showing the last 7 days "
""
"Messages sent within 10 seconds "
"98.31% on average "
"Messages sent within 10 seconds "
"Date Percentage "
- "27 February 2021 98.60% "
- "26 February 2021 100.00% "
- "25 February 2021 99.99% "
- "24 February 2021 100.00% "
- "23 February 2021 95.00% "
- "22 February 2021 95.30% "
- "21 February 2021 99.25% "
+ "February 27, 2021 98.60% "
+ "February 26, 2021 100.00% "
+ "February 25, 2021 99.99% "
+ "February 24, 2021 100.00% "
+ "February 23, 2021 95.00% "
+ "February 22, 2021 95.30% "
+ "February 21, 2021 99.25% "
"Only showing the last 7 days "
""
"Organizations using Notify "
diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py
index e7bfc9327..d5fa744c6 100644
--- a/tests/app/main/views/test_register.py
+++ b/tests/app/main/views/test_register.py
@@ -30,6 +30,7 @@ def test_logged_in_user_redirects_to_account(
)
+@pytest.mark.skip("Deprecated due to change to login-dot-gov-only registration")
@pytest.mark.parametrize(
"phone_number_to_register_with",
[
@@ -75,14 +76,14 @@ def test_register_creates_new_user_and_redirects_to_continue_page(
== "An email has been sent to notfound@example.gsa.gov."
)
- mock_send_verify_email.assert_called_with(ANY, user_data["email_address"])
- mock_register_user.assert_called_with(
- user_data["name"],
- user_data["email_address"],
- user_data["mobile_number"],
- user_data["password"],
- user_data["auth_type"],
- )
+ # mock_send_verify_email.assert_called_with(ANY, user_data["email_address"])
+ # mock_register_user.assert_called_with(
+ # user_data["name"],
+ # user_data["email_address"],
+ # user_data["mobile_number"],
+ # user_data["password"],
+ # user_data["auth_type"],
+ # )
def test_register_continue_handles_missing_session_sensibly(
diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py
index a33f975ba..e15772833 100644
--- a/tests/app/main/views/test_send.py
+++ b/tests/app/main/views/test_send.py
@@ -975,11 +975,11 @@ def test_upload_valid_csv_shows_preview_and_table(
for row_index, row in enumerate(
[
(
- ' 2028675301
',
- ' A
',
+ ' 2028675301
',
+ ' A
',
(
' '
- ' '
+ '
'
"
"
@@ -988,11 +988,11 @@ def test_upload_valid_csv_shows_preview_and_table(
),
),
(
- '
2028675302
',
- '
B
',
+ '
2028675302
',
+ '
B
',
(
'
'
- ' '
+ '
'
"
"
@@ -1001,11 +1001,11 @@ def test_upload_valid_csv_shows_preview_and_table(
),
),
(
- '
2028675303
',
- '
C
',
+ '
2028675303
',
+ '
C
',
(
'
'
- ' '
+ '
'
"
"
@@ -2030,7 +2030,7 @@ def test_create_job_should_call_api(
_expected_status=200,
)
- assert original_file_name in page.text
+ assert "Message status" in page.text
mock_create_job.assert_called_with(
job_id,
diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py
index 5ee810f33..4634a65cf 100644
--- a/tests/app/test_navigation.py
+++ b/tests/app/test_navigation.py
@@ -166,6 +166,7 @@ EXCLUDED_ENDPOINTS = tuple(
"send_one_off",
"send_one_off_step",
"send_one_off_to_myself",
+ "set_up_your_profile",
"service_add_email_reply_to",
"service_add_sms_sender",
"service_confirm_delete_email_reply_to",
@@ -400,7 +401,7 @@ def test_navigation_urls(
assert [a["href"] for a in page.select(".nav a")] == [
"/services/{}/templates".format(SERVICE_ONE_ID),
"/services/{}".format(SERVICE_ONE_ID),
- "/services/{}/usage".format(SERVICE_ONE_ID),
+ # "/services/{}/usage".format(SERVICE_ONE_ID),
# "/services/{}/users".format(SERVICE_ONE_ID),
# "/services/{}/service-settings".format(SERVICE_ONE_ID),
# '/services/{}/api'.format(SERVICE_ONE_ID),