Compare commits

...

51 Commits

Author SHA1 Message Date
Andrew Shumway
0d94e45da3 poetry lock 2024-05-21 11:41:27 -06:00
Andrew Shumway
3a6e5e9c2d Pull main 2024-05-20 10:25:43 -06:00
Andrew Shumway
ed827d0c29 Fix tests 2024-05-20 10:24:50 -06:00
Steven Reilly
8e0724effa Merge pull request #1548 from GSA/fix-utils-deps
Add missing dependencies
2024-05-20 09:34:51 -04:00
Carlo Costino
3caf1e1631 Merge pull request #1539 from GSA/testing-chart.js
Installing chart.js
2024-05-17 18:16:28 -04:00
Carlo Costino
6ce5856f3b Add missing dependencies
The merging of notifications_utils to this repo does not deploy because of missing dependencies.  This changeset adds them back in directly.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-05-17 18:13:09 -04:00
Beverly Nguyen
f91756d2c3 moved script to base.html 2024-05-17 15:07:05 -07:00
Carlo Costino
1b6741d0e5 Merge pull request #1543 from GSA/notify-admin-1396
Exception Investigation: app.notify_client:InviteTokenError
2024-05-17 17:43:06 -04:00
Carlo Costino
fe9e25ba6f Merge pull request #1542 from GSA/add-utils
Localize notification_utils to the admin
2024-05-17 17:26:58 -04:00
Carlo Costino
aa175aa4be Removing empty test file.
Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-05-17 17:15:33 -04:00
Carlo Costino
6f59cff0c4 Merge pull request #1547 from GSA/admin-1534
[ADMIN-1534] API Research - Allowance Count Down
2024-05-17 16:25:37 -04:00
Kenneth Kehl
33d402f9b9 noqa the last one 2024-05-17 12:36:42 -07:00
Beverly Nguyen
90d3dde6b5 Merge branch 'main' into testing-chart.js 2024-05-17 11:26:45 -07:00
Beverly Nguyen
68168a85a1 Merge branch 'main' into testing-chart.js 2024-05-17 11:14:14 -07:00
Carlo Costino
3a42aafad2 Merge pull request #1470 from GSA/1463-convert-basehtml-template-to-become-the-new-main-template
Switch to the new base.html template
2024-05-17 14:11:04 -04:00
Kenneth Kehl
ec6bdee427 almost done 2024-05-17 11:05:22 -07:00
Kenneth Kehl
1b172553f5 more fixes 2024-05-17 10:25:09 -07:00
Cliff Hill
a0e17816e6 Added documentation of findings.
Signed-off-by: Cliff Hill <clifford.hill@gsa.gov>
2024-05-17 12:41:30 -04:00
Kenneth Kehl
7302e4c811 more fixes 2024-05-17 08:52:23 -07:00
Kenneth Kehl
dcfc78d805 some fixes 2024-05-16 15:13:45 -07:00
Kenneth Kehl
e660541e9c Exception Investigation: app.notify_client:InviteTokenError 2024-05-16 09:11:11 -07:00
Carlo Costino
9a83ba7475 Localize notification_utils to the admin
This changeset pulls in all of the notification_utils code directly into the admin and removes it as an external dependency. We are doing this to cut down on operational maintenance of the project and will begin removing parts of it no longer needed for the admin.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-05-16 10:37:37 -04:00
Beverly Nguyen
7ff0cec147 installing chart.js via npm 2024-05-15 15:43:49 -07:00
Beverly Nguyen
d4a46ae552 installing chart.js via npm 2024-05-15 15:40:15 -07:00
Beverly Nguyen
6855a6ebe4 installing chart.js via npm 2024-05-15 15:35:39 -07:00
Beverly Nguyen
5546de66c4 testing chart plain vanilla 2024-05-15 11:39:03 -07:00
Carlo Costino
6bab682edd Merge pull request #1538 from GSA/973-update-config-for-cloud-gov
Updated config files to split the cloud foundry and public admin routes
2024-05-15 09:58:09 -04:00
Anastasia Gradova
e91723fc26 Updated config files to split the cloud foundry
route and the public admin route
2024-05-14 18:45:50 -06:00
Carlo Costino
b3a401a4c7 Merge pull request #1537 from GSA/update-deps-05-14-24
Update dependencies (05/14/2024)
2024-05-14 15:53:24 -04:00
Carlo Costino
d484c3636b Update dependencies (05/14/2024)
This changeset updates several dependencies flagged by Dependabot that we cannot merge directly due to the E2E test issue.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-05-14 15:42:08 -04:00
Andrew Shumway
013154d5cf Refactor create/start job 2024-05-14 09:11:19 -06:00
Carlo Costino
b5c7a71215 Merge pull request #1523 from GSA/notify-admin-1501
Users are still asked to complete their profile when attempting to accept an invite for someone else
2024-05-10 17:28:43 -04:00
Andrew Shumway
8a6802be8f pull main 2024-05-10 10:42:32 -06:00
Kenneth Kehl
e973c11d3b write new tests 2024-05-10 07:52:07 -07:00
Kenneth Kehl
c578175e53 write new tests 2024-05-10 07:51:32 -07:00
Kenneth Kehl
11beff2541 get it working 2024-05-09 14:04:30 -07:00
Kenneth Kehl
7f72599401 notify-admin-1501 2024-05-09 13:08:34 -07:00
Andrew Shumway
5506da56e2 poetry lock 2024-05-08 09:05:27 -06:00
Andrew Shumway
cdd71fa07d pull down main 2024-05-08 09:03:24 -06:00
Beverly Nguyen
065f1b7c15 removed comments on base.html 2024-05-03 10:58:06 -07:00
Andrew Shumway
7108c5f1c8 pull main down 2024-04-30 10:06:49 -06:00
Andrew Shumway
65899f6171 poetry lock 2024-04-26 10:15:44 -06:00
Andrew Shumway
bd633ddc98 Merge branch 'main' of https://github.com/GSA/notifications-admin 2024-04-25 15:27:31 -06:00
Beverly Nguyen
bc945c9420 replaced content_tempalte and withoutnav_template with base.html 2024-04-24 12:31:00 -07:00
Andrew Shumway
ff1956ce70 Merge branch 'main' of https://github.com/GSA/notifications-admin 2024-04-24 09:29:58 -06:00
Beverly Nguyen
2c0a4f16f2 fixed testing 2024-04-23 16:23:31 -07:00
Beverly Nguyen
d47fbb8eb7 switched base.html in 2024-04-23 14:58:44 -07:00
Andrew Shumway
98f12980ad poetry lock 2024-04-22 10:48:46 -06:00
Andrew Shumway
2d3dbbc532 Merge branch 'main' of https://github.com/GSA/notifications-admin 2024-04-22 10:44:17 -06:00
Andrew Shumway
fcf2e31911 Merge branch 'main' of https://github.com/GSA/notifications-admin 2024-04-17 10:42:18 -06:00
Andrew Shumway
2454a6e94b poetry lock 2024-04-15 10:38:27 -06:00
211 changed files with 50427 additions and 626 deletions

View File

@@ -71,7 +71,7 @@ too-complex:
.PHONY: py-test
py-test: export NEW_RELIC_ENVIRONMENT=test
py-test: ## Run python unit tests
poetry run coverage run --omit=*/notifications_utils/* -m pytest --maxfail=10 --ignore=tests/end_to_end tests/
poetry run coverage run -m pytest --maxfail=10 --ignore=tests/end_to_end tests/
poetry run coverage report --fail-under=96
poetry run coverage html -d .coverage_cache
@@ -101,13 +101,6 @@ py-lock: ## Syncs dependencies and updates lock file without performing recursiv
poetry lock --no-update
poetry install --sync
.PHONY: update-utils
update-utils: ## Forces Poetry to pull the latest changes from the notifications-utils repo; requires that you commit the changes to poetry.lock!
poetry update notifications-utils
@echo
@echo !!! PLEASE MAKE SURE TO COMMIT AND PUSH THE UPDATED poetry.lock FILE !!!
@echo
.PHONY: freeze-requirements
freeze-requirements: ## create static requirements.txt
poetry export --without-hashes --format=requirements.txt > requirements.txt

View File

@@ -19,7 +19,6 @@ UI's backend and is required for most things to function. Set that up first!
Our other repositories are:
- [notifications-admin](https://github.com/GSA/notifications-admin)
- [notifications-utils](https://github.com/GSA/notifications-utils)
- [us-notify-compliance](https://github.com/GSA/us-notify-compliance/)
- [notify-python-demo](https://github.com/GSA/notify-python-demo)
@@ -420,23 +419,6 @@ In either situation, once you are finished and have verified the dependency
changes are working, please be sure to commit both the `pyproject.toml` and
`poetry.lock` files.
### Keeping the notification-utils Dependency Up-to-Date
The `notifications-utils` dependency references the other repository we have at
https://github.com/GSA/notifications-utils - this dependency requires a bit of
extra legwork to ensure it stays up-to-date.
Whenever a PR is merged in the `notifications-utils` repository, we need to make
sure the changes are pulled in here and committed to this repository as well.
You can do this by going through these steps:
- Make sure your local `main` branch is up-to-date
- Create a new branch to work in
- Run `make update-utils`
- Commit the updated `poetry.lock` file and push the changes
- Make a new PR with the change
- Have the PR get reviewed and merged
## Known Installation Issues
### Python Installation Errors

View File

@@ -23,12 +23,6 @@ from flask_wtf import CSRFProtect
from flask_wtf.csrf import CSRFError
from itsdangerous import BadSignature
from notifications_python_client.errors import HTTPError
from notifications_utils import logging, request_helper
from notifications_utils.formatters import (
formatted_list,
get_lines_with_normalised_whitespace,
)
from notifications_utils.recipients import format_phone_number_human_readable
from werkzeug.exceptions import HTTPException as WerkzeugHTTPException
from werkzeug.exceptions import abort
from werkzeug.local import LocalProxy
@@ -114,6 +108,12 @@ from app.notify_client.upload_api_client import upload_api_client
from app.notify_client.user_api_client import user_api_client
from app.url_converters import SimpleDateTypeConverter, TemplateTypeConverter
from app.utils.govuk_frontend_jinja.flask_ext import init_govuk_frontend
from notifications_utils import logging, request_helper
from notifications_utils.formatters import (
formatted_list,
get_lines_with_normalised_whitespace,
)
from notifications_utils.recipients import format_phone_number_human_readable
login_manager = LoginManager()
csrf = CSRFProtect()

View File

@@ -0,0 +1,24 @@
(function (window) {
const ctx = document.getElementById('myChart');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
})(window);

View File

@@ -2,9 +2,9 @@ import json
from os import getenv
import newrelic.agent
from notifications_utils import DAILY_MESSAGE_LIMIT
from app.cloudfoundry_config import cloud_config
from notifications_utils import DAILY_MESSAGE_LIMIT
class Config(object):

View File

@@ -16,15 +16,15 @@ from bs4 import BeautifulSoup
from flask import render_template_string, url_for
from flask.helpers import get_root_path
from markupsafe import Markup
from app.utils.csv import get_user_preferred_timezone
from app.utils.time import parse_naive_dt
from notifications_utils.field import Field
from notifications_utils.formatters import make_quotes_smart
from notifications_utils.formatters import nl2br as utils_nl2br
from notifications_utils.recipients import InvalidPhoneError, validate_phone_number
from notifications_utils.take import Take
from app.utils.csv import get_user_preferred_timezone
from app.utils.time import parse_naive_dt
def apply_html_class(tags, html_file):
new_html = html_file

View File

@@ -11,9 +11,6 @@ from flask_wtf.file import FileAllowed
from flask_wtf.file import FileField as FileField_wtf
from flask_wtf.file import FileSize
from markupsafe import Markup
from notifications_utils.formatters import strip_all_whitespace
from notifications_utils.insensitive_dict import InsensitiveDict
from notifications_utils.recipients import InvalidPhoneError, validate_phone_number
from werkzeug.utils import cached_property
from wtforms import (
BooleanField,
@@ -66,6 +63,9 @@ from app.models.organization import Organization
from app.utils import merge_jsonlike
from app.utils.csv import get_user_preferred_timezone
from app.utils.user_permissions import all_ui_permissions, permission_options
from notifications_utils.formatters import strip_all_whitespace
from notifications_utils.insensitive_dict import InsensitiveDict
from notifications_utils.recipients import InvalidPhoneError, validate_phone_number
def get_time_value_and_label(future_time):

View File

@@ -1,15 +1,15 @@
import re
from abc import ABC, abstractmethod
from notifications_utils.field import Field
from notifications_utils.formatters import formatted_list
from notifications_utils.recipients import InvalidEmailError, validate_email_address
from notifications_utils.sanitise_text import SanitiseSMS
from wtforms import ValidationError
from app.main._commonly_used_passwords import commonly_used_passwords
from app.models.spreadsheet import Spreadsheet
from app.utils.user import is_gov_user
from notifications_utils.field import Field
from notifications_utils.formatters import formatted_list
from notifications_utils.recipients import InvalidEmailError, validate_email_address
from notifications_utils.sanitise_text import SanitiseSMS
class CommonlyUsedPassword:

View File

@@ -1,14 +1,14 @@
from flask import jsonify, redirect, render_template, session, url_for
from flask_login import current_user
from notifications_python_client.errors import HTTPError
from notifications_utils.recipients import format_phone_number_human_readable
from notifications_utils.template import SMSPreviewTemplate
from app import current_service, notification_api_client, service_api_client
from app.main import main
from app.main.forms import SearchByNameForm
from app.models.template_list import TemplateList
from app.utils.user import user_has_permissions
from notifications_utils.recipients import format_phone_number_human_readable
from notifications_utils.template import SMSPreviewTemplate
@main.route("/services/<uuid:service_id>/conversation/<uuid:notification_id>")

View File

@@ -6,7 +6,6 @@ from itertools import groupby
from flask import Response, abort, jsonify, render_template, request, session, url_for
from flask_login import current_user
from notifications_utils.recipients import format_phone_number_human_readable
from werkzeug.utils import redirect
from app import (
@@ -30,6 +29,7 @@ from app.utils.csv import Spreadsheet
from app.utils.pagination import generate_next_dict, generate_previous_dict
from app.utils.time import get_current_financial_year
from app.utils.user import user_has_permissions
from notifications_utils.recipients import format_phone_number_human_readable
@main.route("/services/<uuid:service_id>/dashboard")

View File

@@ -15,7 +15,6 @@ from flask import (
)
from flask_login import current_user
from markupsafe import Markup
from notifications_utils.template import EmailPreviewTemplate, SMSBodyPreviewTemplate
from app import (
current_service,
@@ -35,6 +34,7 @@ from app.utils.pagination import (
get_page_from_request,
)
from app.utils.user import user_has_permissions
from notifications_utils.template import EmailPreviewTemplate, SMSBodyPreviewTemplate
@main.route("/services/<uuid:service_id>/jobs")

View File

@@ -10,12 +10,12 @@ from flask import (
url_for,
)
from itsdangerous import SignatureExpired
from notifications_utils.url_safe_token import check_token
from app.main import main
from app.main.forms import NewPasswordForm
from app.models.user import User
from app.utils.login import log_in_user
from notifications_utils.url_safe_token import check_token
@main.route("/new-password/<path:token>", methods=["GET", "POST"])

View File

@@ -1,11 +1,11 @@
from flask import current_app, render_template
from flask_login import current_user
from notifications_utils.international_billing_rates import INTERNATIONAL_BILLING_RATES
from app.main import main
from app.main.forms import SearchByNameForm
from app.main.views.sub_navigation_dictionaries import using_notify_nav
from app.utils.user import user_is_logged_in
from notifications_utils.international_billing_rates import INTERNATIONAL_BILLING_RATES
CURRENT_SMS_RATE = "1.72"

View File

@@ -15,7 +15,7 @@ from flask import (
)
from flask_login import current_user
from app import user_api_client
from app import redis_client, user_api_client
from app.main import main
from app.main.forms import (
RegisterUserForm,
@@ -113,74 +113,89 @@ def registration_continue():
raise Exception("Unexpected routing in registration_continue")
def get_invite_data_from_redis(state):
invite_data = json.loads(redis_client.raw_get(f"invitedata-{state}"))
user_email = redis_client.raw_get(f"user_email-{state}").decode("utf8")
user_uuid = redis_client.raw_get(f"user_uuid-{state}").decode("utf8")
invited_user_email_address = redis_client.raw_get(
f"invited_user_email_address-{state}"
).decode("utf8")
return invite_data, user_email, user_uuid, invited_user_email_address
def put_invite_data_in_redis(
state, invite_data, user_email, user_uuid, invited_user_email_address
):
ttl = 60 * 15 # 15 minutes
redis_client.raw_set(f"invitedata-{state}", json.dumps(invite_data), ex=ttl)
redis_client.raw_set(f"user_email-{state}", user_email, ex=ttl)
redis_client.raw_set(f"user_uuid-{state}", user_uuid, ex=ttl)
redis_client.raw_set(
f"invited_user_email_address-{state}",
invited_user_email_address,
ex=ttl,
)
def check_invited_user_email_address_matches_expected(
user_email, invited_user_email_address
):
if user_email.lower() != invited_user_email_address.lower():
debug_msg("invited user email did not match expected email, abort(403)")
flash("You cannot accept an invite for another person.")
abort(403)
@main.route("/set-up-your-profile", methods=["GET", "POST"])
@hide_from_search_engines
def set_up_your_profile():
form = SetupUserProfileForm()
debug_msg(f"Enter set_up_your_profile with request.args {request.args}")
code = request.args.get("code")
state = request.args.get("state")
login_gov_error = request.args.get("error")
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:
return _handle_login_dot_gov_invite(code, state, form)
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
if redis_client.raw_get(f"invitedata-{state}") is None:
access_token = sign_in._get_access_token(code, state)
debug_msg("Got the access token for login.gov")
user_email, user_uuid = sign_in._get_user_email_and_uuid(access_token)
debug_msg(
f"Got the user_email {user_email} and user_uuid {user_uuid} from login.gov"
)
invite_data = state.encode("utf8")
invite_data = base64.b64decode(invite_data)
invite_data = json.loads(invite_data)
debug_msg(f"final state {invite_data}")
invited_user_id = invite_data["invited_user_id"]
invited_user_email_address = get_invited_user_email_address(invited_user_id)
debug_msg(f"email address from the invite_date is {invited_user_email_address}")
check_invited_user_email_address_matches_expected(
user_email, invited_user_email_address
)
return render_template("views/set-up-your-profile.html", form=form)
def get_invited_user_email_address(invited_user_id):
# InvitedUser is an unhashable type and hard to mock in tests
# so this convenience method is a workaround for that
invited_user = InvitedUser.by_id(invited_user_id)
return invited_user.email_address
def invited_user_accept_invite(invited_user_id):
# InvitedUser is an unhashable type and hard to mock in tests
# so this convenience method is a workaround for that
invited_user = InvitedUser.by_id(invited_user_id)
invited_user.accept_invite()
def debug_msg(msg):
current_app.logger.debug(hilite(msg))
def _handle_login_dot_gov_invite(code, state, form):
debug_msg(f"enter _handle_login_dot_gov_invite with code {code} state {state}")
access_token = sign_in._get_access_token(code, state)
debug_msg("Got the access token for login.gov")
user_email, user_uuid = sign_in._get_user_email_and_uuid(access_token)
debug_msg(
f"Got the user_email {user_email} and user_uuid {user_uuid} from login.gov"
)
debug_msg(f"raw state {state}")
invite_data = state.encode("utf8")
debug_msg(f"utf8 encoded state {invite_data}")
invite_data = base64.b64decode(invite_data)
debug_msg(f"b64 decoded state {invite_data}")
invite_data = json.loads(invite_data)
debug_msg(f"final state {invite_data}")
invited_user_id = invite_data["invited_user_id"]
invited_user_email_address = get_invited_user_email_address(invited_user_id)
debug_msg(f"email address from the invite_date is {invited_user_email_address}")
if user_email.lower() != invited_user_email_address.lower():
debug_msg("invited user email did not match expected email, abort(403)")
flash("You cannot accept an invite for another person.")
session.pop("invited_user_id", None)
abort(403)
else:
invited_user_accept_invite(invited_user_id)
debug_msg(
f"invited user {invited_user_email_address} to service {invite_data['service_id']}"
f"accepted invite user {invited_user_email_address} to service {invite_data['service_id']}"
)
debug_msg("accepted invite")
# We need to avoid taking a second trip through the login.gov code because we cannot pull the
# access token twice. So once we retrieve these values, let's park them in redis for 15 minutes
put_invite_data_in_redis(
state, invite_data, user_email, user_uuid, invited_user_email_address
)
form = SetupUserProfileForm()
if (
form.validate_on_submit()
and redis_client.raw_get(f"invitedata-{state}") is not None
):
invite_data, user_email, user_uuid, invited_user_email_address = (
get_invite_data_from_redis(state)
)
# create or update the user
user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
if user is None:
user = User.register(
@@ -191,6 +206,9 @@ def _handle_login_dot_gov_invite(code, state, form):
auth_type="sms_auth",
)
debug_msg(f"registered user {form.name.data} with email {user_email}")
else:
user.update(mobile_number=form.mobile_number.data, name=form.name.data)
debug_msg(f"updated user {form.name.data}")
# activate the user
user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
@@ -207,3 +225,32 @@ def _handle_login_dot_gov_invite(code, state, form):
f"Added user {usr.email_address} to service {invite_data['service_id']}"
)
return redirect(url_for("main.show_accounts_or_dashboard"))
elif login_gov_error:
current_app.logger.error(f"login.gov error: {login_gov_error}")
abort(403)
# we take two trips through this method, but should only hit this
# line on the first trip. On the second trip, we should get redirected
# to the accounts page because we have successfully registered.
return render_template("views/set-up-your-profile.html", form=form)
def get_invited_user_email_address(invited_user_id):
# InvitedUser is an unhashable type and hard to mock in tests
# so this convenience method is a workaround for that
invited_user = InvitedUser.by_id(invited_user_id)
return invited_user.email_address
def invited_user_accept_invite(invited_user_id):
invited_user = InvitedUser.by_id(invited_user_id)
if invited_user.status == "expired":
current_app.logger.error("User invitation has expired")
flash("Your invitation has expired.")
abort(401)
invited_user.accept_invite()
def debug_msg(msg):
current_app.logger.debug(hilite(msg))

View File

@@ -7,10 +7,6 @@ from flask import abort, flash, redirect, render_template, request, session, url
from flask_login import current_user
from markupsafe import Markup
from notifications_python_client.errors import HTTPError
from notifications_utils import SMS_CHAR_COUNT_LIMIT
from notifications_utils.insensitive_dict import InsensitiveDict
from notifications_utils.recipients import RecipientCSV, first_column_headings
from notifications_utils.sanitise_text import SanitiseASCII
from xlrd.biffh import XLRDError
from xlrd.xldate import XLDateError
@@ -39,6 +35,10 @@ from app.utils import PermanentRedirect, should_skip_template_page, unicode_trun
from app.utils.csv import Spreadsheet, get_errors_for_csv
from app.utils.templates import get_template
from app.utils.user import user_has_permissions
from notifications_utils import SMS_CHAR_COUNT_LIMIT
from notifications_utils.insensitive_dict import InsensitiveDict
from notifications_utils.recipients import RecipientCSV, first_column_headings
from notifications_utils.sanitise_text import SanitiseASCII
def get_example_csv_fields(column_headers, use_example_as_example, submitted_fields):
@@ -653,17 +653,18 @@ def preview_job(service_id, template_id, upload_id, row_index=2):
data = _check_messages(
service_id, template_id, upload_id, row_index, force_hide_sender=True
)
create_job(service_id, upload_id)
return render_template(
"views/check/preview.html",
scheduled_for=session["scheduled_for"],
scheduled_for=session.get("scheduled_for"),
**data,
)
@main.route("/services/<uuid:service_id>/start-job/<uuid:upload_id>", methods=["POST"])
@main.route("/services/<uuid:service_id>/create-job/<uuid:upload_id>", methods=["POST"])
@user_has_permissions("send_messages", restrict_admin_usage=True)
def start_job(service_id, upload_id):
def create_job(service_id, upload_id):
scheduled_for = session.pop("scheduled_for", None)
job_api_client.create_job(
upload_id,
@@ -671,8 +672,12 @@ def start_job(service_id, upload_id):
scheduled_for=scheduled_for,
)
session.pop("sender_id", None)
@main.route("/services/<uuid:service_id>/start-job/<uuid:upload_id>", methods=["POST"])
@user_has_permissions("send_messages", restrict_admin_usage=True)
def start_job(service_id, upload_id):
job_api_client.start_job(service_id, upload_id)
session.pop("sender_id", None)
return redirect(
url_for(
"main.view_job",
@@ -908,36 +913,7 @@ def preview_notification(service_id, template_id):
template_id=template_id,
)
)
session["scheduled_for"] = request.form.get("scheduled_for", "")
return render_template(
"views/notifications/preview.html",
**_check_notification(
service_id, template_id, show_recipient=False, force_hide_sender=True
),
scheduled_for=session["scheduled_for"],
recipient=recipient,
)
@main.route(
"/services/<uuid:service_id>/template/<uuid:template_id>/notification/check",
methods=["POST"],
)
@user_has_permissions("send_messages", restrict_admin_usage=True)
def send_notification(service_id, template_id):
scheduled_for = session.pop("scheduled_for", "")
recipient = get_recipient()
if not recipient:
return redirect(
url_for(
".send_one_off",
service_id=service_id,
template_id=template_id,
)
)
keys = []
values = []
for k, v in session["placeholders"].items():
@@ -968,10 +944,39 @@ def send_notification(service_id, template_id):
notification_count=1,
valid="True",
)
session["recipient"] = recipient
session["upload_id"] = upload_id
session["scheduled_for"] = request.form.get("scheduled_for", "")
session.pop("recipient")
session.pop("placeholders")
return render_template(
"views/notifications/preview.html",
**_check_notification(
service_id, template_id, show_recipient=False, force_hide_sender=True
),
upload_id=upload_id,
scheduled_for=session["scheduled_for"],
recipient=recipient,
)
@main.route(
"/services/<uuid:service_id>/template/<uuid:template_id>/notification/check",
methods=["POST"],
)
@user_has_permissions("send_messages", restrict_admin_usage=True)
def send_notification(service_id, template_id):
recipient = get_recipient()
if not recipient:
return redirect(
url_for(
".send_one_off",
service_id=service_id,
template_id=template_id,
)
)
upload_id = session.pop("upload_id")
job_api_client.start_job(service_id, upload_id)
# We have to wait for the job to run and create the notification in the database
time.sleep(0.1)
notifications = notification_api_client.get_notifications_for_service(
@@ -1000,12 +1005,15 @@ def send_notification(service_id, template_id):
job_id=upload_id,
)
)
session.pop("recipient")
session.pop("placeholders")
return redirect(
url_for(
".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
# a back link on a just sent one off notification (help=0)
help=request.args.get("help"),

View File

@@ -6,7 +6,6 @@ import jwt
import requests
from flask import Response, current_app, redirect, render_template, request, 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
@@ -16,6 +15,7 @@ from app.models.user import User
from app.utils import hide_from_search_engines
from app.utils.login import is_safe_redirect_url
from app.utils.time import is_less_than_days_ago
from notifications_utils.url_safe_token import generate_token
def _reformat_keystring(orig):

View File

@@ -4,7 +4,6 @@ from flask import abort, flash, jsonify, redirect, render_template, request, url
from flask_login import current_user
from markupsafe import Markup
from notifications_python_client.errors import HTTPError
from notifications_utils import SMS_CHAR_COUNT_LIMIT
from app import (
current_service,
@@ -30,6 +29,7 @@ from app.models.template_list import TemplateList, TemplateLists
from app.utils import NOTIFICATION_TYPES, should_skip_template_page
from app.utils.templates import get_template
from app.utils.user import user_has_permissions
from notifications_utils import SMS_CHAR_COUNT_LIMIT
form_objects = {
"email": EmailTemplateForm,

View File

@@ -3,7 +3,6 @@ import json
from flask import current_app, redirect, render_template, request, session, url_for
from flask_login import current_user
from itsdangerous import SignatureExpired
from notifications_utils.url_safe_token import check_token
from app import user_api_client
from app.main import main
@@ -15,6 +14,7 @@ from app.utils.login import (
redirect_to_sign_in,
redirect_when_logged_in,
)
from notifications_utils.url_safe_token import check_token
@main.route("/two-factor-email-sent", methods=["GET"])

View File

@@ -11,7 +11,6 @@ from flask import (
url_for,
)
from flask_login import current_user
from notifications_utils.url_safe_token import check_token
from app import user_api_client
from app.event_handlers import (
@@ -31,6 +30,7 @@ from app.main.forms import (
)
from app.models.user import User
from app.utils.user import user_is_gov_user, user_is_logged_in
from notifications_utils.url_safe_token import check_token
NEW_EMAIL = "new-email"
NEW_MOBILE = "new-mob"

View File

@@ -2,13 +2,13 @@ import json
from flask import abort, current_app, flash, redirect, render_template, session, url_for
from itsdangerous import SignatureExpired
from notifications_utils.url_safe_token import check_token
from app import user_api_client
from app.main import main
from app.main.forms import TwoFactorForm
from app.models.user import User
from app.utils.login import redirect_to_sign_in
from notifications_utils.url_safe_token import check_token
@main.route("/verify", methods=["GET", "POST"])

View File

@@ -1,6 +1,7 @@
from abc import abstractmethod
from flask import abort
from notifications_utils.serialised_model import (
SerialisedModel,
SerialisedModelCollection,

View File

@@ -1,10 +1,9 @@
from abc import ABC, abstractmethod
from notifications_utils.formatters import formatted_list
from app.formatters import format_thousands
from app.models import ModelList
from app.notify_client.service_api_client import service_api_client
from notifications_utils.formatters import formatted_list
class Event(ABC):

View File

@@ -1,5 +1,4 @@
from flask import abort, current_app
from notifications_utils.serialised_model import SerialisedModelCollection
from werkzeug.utils import cached_property
from app.models import JSONModel, SortByNameMixin
@@ -15,6 +14,7 @@ from app.notify_client.organizations_api_client import organizations_client
from app.notify_client.service_api_client import service_api_client
from app.notify_client.template_folder_api_client import template_folder_api_client
from app.utils import get_default_sms_sender
from notifications_utils.serialised_model import SerialisedModelCollection
class Service(JSONModel, SortByNameMixin):

View File

@@ -2,9 +2,9 @@ from flask import abort, has_request_context, request
from flask_login import current_user
from notifications_python_client import __version__
from notifications_python_client.base import BaseAPIClient
from notifications_utils.clients.redis import RequestCache
from app.extensions import redis_client
from notifications_utils.clients.redis import RequestCache
cache = RequestCache(redis_client)

View File

@@ -147,5 +147,10 @@ class JobApiClient(NotifyAdminAPIClient):
url="/service/{}/job/{}/cancel".format(service_id, job_id), data={}
)
def start_job(self, service_id, job_id):
return self.post(
url="/service/{}/job/{}/start-job".format(service_id, job_id), data={}
)
job_api_client = JobApiClient()

View File

@@ -37,11 +37,10 @@ class NotificationApiClient(NotifyAdminAPIClient):
# we do not want in our logs, so we do a POST request instead of a GET
method = self.post if to else self.get
kwargs = {"data": params} if to else {"params": params}
if job_id:
return method(
url="/service/{}/job/{}/notifications".format(service_id, job_id),
**kwargs
**kwargs,
)
else:
if limit_days is not None:
@@ -100,5 +99,8 @@ class NotificationApiClient(NotifyAdminAPIClient):
url="/service/{}/job/{}/notification_count".format(service_id, job_id)
)["count"]
def get_total_notification_message_parts_by_job_id(self, job_id):
return self.get(url="/notifications/{}/message_parts".format(job_id))
notification_api_client = NotificationApiClient()

View File

@@ -1,7 +1,6 @@
import uuid
from flask import current_app
from notifications_utils.s3 import s3upload as utils_s3upload
from app.s3_client import (
get_s3_contents,
@@ -9,6 +8,7 @@ from app.s3_client import (
get_s3_object,
set_s3_metadata,
)
from notifications_utils.s3 import s3upload as utils_s3upload
FILE_LOCATION_STRUCTURE = "service-{}-notify/{}.csv"

View File

@@ -2,9 +2,9 @@ import uuid
from boto3 import Session
from flask import current_app
from notifications_utils.s3 import s3upload as utils_s3upload
from app.s3_client import get_s3_object
from notifications_utils.s3 import s3upload as utils_s3upload
TEMP_TAG = "temp-{user_id}_"
EMAIL_LOGO_LOCATION_STRUCTURE = "{temp}{unique_id}-{filename}"

View File

@@ -1,10 +1,17 @@
{% from "../components/banner.html" import banner %}
{% from "../components/components/skip-link/macro.njk" import usaSkipLink -%}
{% from "components/banner.html" import banner %}
{% from "components/components/skip-link/macro.njk" import usaSkipLink -%}
{% from "components/sub-navigation.html" import sub_navigation %}
<!DOCTYPE html>
<html lang="{{ htmlLang | default('en') }}" class="{{ htmlClasses }}">
{% include "new/components/head.html" %}
<head>
<title>
{% block pageTitle %}
{% block per_page_title %}{% endblock %} Notify.gov
{% endblock %}
</title>
{% include "new/components/head.html" %}
</head>
<body class="usa-template__body {{ bodyClasses }}">
<script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>
{% block bodyStart %}
@@ -23,11 +30,10 @@
}) }}
{% endblock %}
{% block header %}
{% if current_user.is_authenticated %}
{% include 'new/components/usa_banner.html' %}
{% include 'new/components/header.html' %}
{% endif %}
{% include 'new/components/usa_banner.html' %}
{% include 'new/components/header.html' %}
{% endblock %}
{% block main %}
@@ -36,18 +42,17 @@
{% block backLink %}{% endblock %}
{% endblock %}
{% block mainClasses %}
<!-- notes set mainClasses = "margin-top-5 padding-bottom-5" where withoutnav_template was used and maybe templates that are using content_template -->
{% set mainClasses = "margin-top-5 padding-bottom-5" %}
<main class="{{ mainClasses }}" id="main-content" role="main">
{% endblock %}
{% block content %}
{% block flash_messages %}
<!-- flash_message.html was from the withoutnav_template and is only included on child templates that was using withoutnav_template. Now, we can add in flash_message blocks and include 'flash_messages.html' to child templates that was using withoutnav_template. This will help to eliminate the use of a whole other parent template. -->
{% include 'flash_messages.html' %}
{% endblock %}
{% block maincolumn_content %}
{% block fromContentTemplatetwoColumnGrid %}
<div class="grid-row">
{% if navigation_links %}
<div class="tablet:grid-col-2">
<div class="tablet:grid-col-2 margin-bottom-4">
{{ sub_navigation(navigation_links) }}
</div>
<div class="tablet:grid-col-10 padding-left-4 usa-prose site-prose">
@@ -57,8 +62,6 @@
{% block content_column_content %}{% endblock %}
</div>
</div>
<!-- content_column_content block is from the content_template.html. We do not need this template. We can consolidate and move it to the base.html template. We can call on this block where child templates were using the content_template.html -->
{% endblock %}
{% endblock %}
{% endblock %}
</main>

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}Youre not authorised to see this page{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}Youre not allowed to see this page{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}Page not found{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}Page not found{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}Sorry, theres a problem with the service{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">

View File

@@ -1,6 +1,4 @@
<head>
<meta charset="utf-8" />
<title>{% block pageTitle %}Notify.gov{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Reach people where they are with a text message platform built just for government">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="f0f0f0" />
@@ -12,7 +10,7 @@
{# Ensure that older IE versions always render with the correct rendering engine #}
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
{% block headIcons %}
<link rel="shortcut icon" href="{{ assetPath | default('/assets') }}/images/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="{{ assetPath | default('/assets') }}/images/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="{{ assetPath | default('/assets') }}/images/favicon-16x16.png" />
@@ -22,25 +20,19 @@
<meta name="msapplication-TileColor" content="#da532c">
<link href="{{ assetPath | default('/assets') }}/images/notify-dark-favicon.png" rel="icon" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#ffffff">
{% endblock %}
<link rel="stylesheet" media="screen" href="{{ asset_url('css/styles.css') }}" />
{% block extra_stylesheets %}{% endblock %}
{% block meta_format_detection %}
<meta name="format-detection" content="telephone=no">
{% endblock %}
{% block og_image %}
<meta property="og:title" content="Notify.gov">
<meta property="og:description" content="Reach people where they are with a text message platform built just for government">
<meta property="og:image" content="/static/images/notify-og-image.png">
{% endblock %}
{# google #}
<script type="text/javascript" src="{{ asset_url('js/gtm_head.js') }}"></script>
<script type="text/javascript" src="{{ url_for('static', filename='js/chart.umd.js') }}"></script>
<meta name="google-site-verification" content="niWnSqImOWz6mVQTYqNb5tFK8HaKSB4b3ED4Z9gtUQ0" />
{% if g.hide_from_search_engines %}
<meta name="robots" content="noindex" />
{% endif %}
{% block head %}{% endblock %}
</head>

View File

@@ -1,25 +1,28 @@
{# 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"}
{% if current_user.is_authenticated %}
{% set navigation = [
{"href": url_for("main.show_accounts_or_dashboard"), "text": "Current service", "active": header_navigation.is_selected('accounts-or-dashboard')},
{"href": url_for('main.get_started'), "text": "Using Notify", "active": header_navigation.is_selected('using_notify')},
{"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')}
] %}
{% else %}
{% set secondaryNavigation = [{"href": url_for('main.sign_out'), "text": "Sign out"}] %}
{% if current_user.platform_admin %}
{% set navigation = navigation + [{"href": url_for('main.platform_admin_splash_page'), "text": "Platform admin", "active": header_navigation.is_selected('platform-admin')}] %}
{% else %}
{% set navigation = navigation + [{"href": url_for('main.user_profile'), "text": "User profile", "active": header_navigation.is_selected('user-profile')}] %}
{% endif %}
{% if current_service %}
{% 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 %}
{# usa header #}
<header class="usa-header usa-header--extended">
<div class="usa-nav-container">

View File

@@ -6,10 +6,9 @@ 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`, `settings_templates`, and `content_template`.
- `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 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
- `withnav_template.html`: A variation of org_template and settings_template that includes a sidebar.
- **/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)
- `head.html`: Template for the site's <head>, included in `base.html`.
- `header.html`: Template for the site's header, included in `base.html`.
@@ -23,20 +22,15 @@ 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.
### 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. This is also true for `usaHeader` macro component.
- Employ **components** `{% include %}` for reusable UI elements.
### Old Layout Templates We Don't Need
- withoutnav_template.html Delete
- main_template.html Delete
- withoutnav_template.html Delete, it is now apart of base.html
- main_template.html Delete, it is now apart of base.html
- settings_templates.html `withnav_template` can be used to replace `settings_template`.
- settings_nav.html (move to /components/ directory)
- main_nav.html (move to /components/ directory)
- service_navigation.html (move to /components/ directory)
- org_template, could be under it's own directory called /layout/organization
- org_template, Delete, it is now apart of withnav_template.html
- org_nav.html (move to /components/ directory)
- content_template.html Delete
- content_template.html Delete, it is now apart of base.html

View File

@@ -291,10 +291,12 @@
{% block bodyEnd %}
{% block extra_javascripts %}
{% endblock %}
<!--[if gt IE 8]><!-->
<script type="text/javascript" src="{{ asset_url('javascripts/all.js') }}"></script>
<script type="text/javascript" src="{{ asset_url('js/uswds.min.js') }}"></script>
<!--<![endif]-->
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/sub-navigation.html" import sub_navigation %}
{% from "components/page-header.html" import page_header %}

View File

@@ -1,4 +1,4 @@
{% extends "admin_template.html" %}
{% extends "base.html" %}
{% set mainClasses = "margin-top-5 padding-bottom-5" %}

View File

@@ -1,4 +1,4 @@
{% extends "admin_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
{% block org_page_title %}{% endblock %} {{ current_org.name }}

View File

@@ -1,4 +1,4 @@
{% extends "admin_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
{% block service_page_title %}{% endblock %} {{ current_service.name }}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/service-link.html" import service_link %}
{% from "components/content-metadata.html" import content_metadata %}
@@ -168,7 +168,7 @@
<p>
The following content on the <a class="usa-link" href="https://status.notifications.service.gov.uk/">Notify status page</a> is not compliant with the WCAG version 2.1 AA standard:
</p>
</p>
<ul class="usa-list">
<li>an incorrect heading hierarchy which fails <a class="usa-link" href="https://www.w3.org/TR/WCAG21/#info-and-relationships">success criterion 1.3.1: info and relationships</a></li>

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}The invitation you were sent has been cancelled{% endblock %}
{% block maincolumn_content %}
<div class="grid-row">

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
Check your email

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/components/button/macro.njk" import usaButton %}
{% macro service_list(

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
Documentation

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/components/button/macro.njk" import usaButton %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/table.html" import mapping_table, row, text_field, edit_field, field with context %}
{% from "components/sub-navigation.html" import sub_navigation %}
{% from "components/service-link.html" import service_link %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/table.html" import mapping_table, row, text_field, edit_field, field with context %}
{% from "components/service-link.html" import service_link %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/table.html" import mapping_table, row, text_field, edit_field, field with context %}
{% from "components/sub-navigation.html" import sub_navigation %}
{% from "components/components/details/macro.njk" import usaDetails %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/service-link.html" import service_link %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/service-link.html" import service_link %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/table.html" import mapping_table, row, text_field, edit_field, field with context %}
{% from "components/sub-navigation.html" import sub_navigation %}
{% from "components/service-link.html" import service_link %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
Send files by email

View File

@@ -1,5 +1,5 @@
{% from "components/table.html" import mapping_table, row, text_field, edit_field, field with context %}
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
Integration testing

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/table.html" import mapping_table, row, text_field %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -61,6 +61,7 @@
)}}" class='page-footer'>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<!-- <p>Placeholder: This message will be delivered to <b>400 phone numbers</b> and will use a total of <b>800 message parts</b>, leaving Washington DSHS with <b>249,200 message parts remaining</b>.</p> -->
<h3>Does everything look good?</h3>
{% if not error %}
{% set button_text %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
Check your email

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/big-number.html" import big_number %}
{% from "components/page-header.html" import page_header %}
{% from "components/table.html" import field, list_table %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% block main %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/copy-to-clipboard.html" import copy_to_clipboard %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% block per_page_title %}

View File

@@ -3,7 +3,7 @@
{% from "components/live-search.html" import live_search %}
{% from "components/components/details/macro.njk" import usaDetails %}
{% extends "content_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
Message parts

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/content-metadata.html" import content_metadata %}
{% block per_page_title %}
@@ -61,7 +61,7 @@
processed and when its stored.</p>
<!-- <p>Your personal data will be processed both in the UK and the European Economic Area (EEA). Your data receives the same level of protection in the EEA as it does in the UK through the safeguard of <a class="usa-link" href="https://ico.org.uk/for-organizations/dp-at-the-end-of-the-transition-period/data-protection-and-the-eu-in-detail/adequacy/">Adequacy Decisions</a>.</p> -->
<h2 class="font-body-lg" id="how-we-protect-your-data">How we protect your data and keep it secure</h2>
<p>We are committed to doing all that we can to keep your data secure. To prevent unauthorized access or disclosure we have put in place technical and organizational procedures to secure the data we collect about you for example, we protect your
@@ -70,7 +70,7 @@
<!-- <h2 class="font-body-lg" id="childrens-privacy-protection">Childrens privacy protection</h2>
<p>We understand the importance of protecting childrens privacy online. Our services are not designed for, or intentionally targeted at, children 13 years of age or younger. It is not our policy to intentionally collect or maintain data about anyone under the age of 13.</p>
<p>We understand the importance of protecting childrens privacy online. Our services are not designed for, or intentionally targeted at, children 13 years of age or younger. It is not our policy to intentionally collect or maintain data about anyone under the age of 13.</p>
<h2 class="font-body-lg" id="your-rights">What are your rights</h2>

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
Check your email

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/table.html" import mapping_table, row, text_field, edit_field, field with context %}
{% from "components/content-metadata.html" import content_metadata %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/table.html" import mapping_table, row, text_field, edit_field, field with context %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}
{% from "components/components/select/macro.njk" import usaSelect -%}

View File

@@ -1,4 +1,4 @@
{% extends "admin_template.html" %}
{% extends "base.html" %}
{% from "components/components/button/macro.njk" import usaButton %}
{% block meta %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% from "components/banner.html" import banner_wrapper %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "content_template.html" %}
{% extends "base.html" %}
{% block per_page_title %}
Trial mode

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% block per_page_title %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/components/button/macro.njk" import usaButton %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/form.html" import form_wrapper %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/page-footer.html" import page_footer %}
{% from "components/form.html" import form_wrapper %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/page-header.html" import page_header %}
{% from "components/components/button/macro.njk" import usaButton %}
{% from "components/components/back-link/macro.njk" import usaBackLink %}

View File

@@ -1,4 +1,4 @@
{% extends "withoutnav_template.html" %}
{% extends "base.html" %}
{% from "components/sub-navigation.html" import sub_navigation %}
{% block per_page_title %}

Some files were not shown because too many files have changed in this diff Show More