diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..0086358db
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: true
diff --git a/.github/ISSUE_TEMPLATE/issue_template.yml b/.github/ISSUE_TEMPLATE/issue_template.yml
new file mode 100644
index 000000000..576af0095
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/issue_template.yml
@@ -0,0 +1,76 @@
+name: "User Story Template"
+description: "Use this template for creating user stories"
+title: "User Story: [Brief description of the user story]"
+
+labels:
+ - "Type: User Story"
+
+body:
+ - type: markdown
+ attributes:
+ value: '**User Story:**'
+ - type: textarea
+ id: userType
+ attributes:
+ label: "As a [type of user],"
+ description: "Describe the type of user involved in this story."
+ validations:
+ required: true
+ - type: textarea
+ id: actionFeature
+ attributes:
+ label: "I want [an action or feature],"
+ description: "Describe the desired action or feature from the user's perspective."
+ validations:
+ required: true
+ - type: textarea
+ id: benefitValue
+ attributes:
+ label: "So that [benefit or value]."
+ description: "Describe the benefit or value the user expects from the action or feature."
+ validations:
+ required: true
+
+ - type: markdown
+ attributes:
+ value: '**Acceptance Criteria:**'
+ - type: textarea
+ id: acceptanceCriteria
+ attributes:
+ label: "Detailed condition or criteria that must be met for the user story to be considered complete."
+ description: "List the acceptance criteria for the user story."
+ validations:
+ required: true
+
+ - type: markdown
+ attributes:
+ value: '**Tasks:**'
+ - type: textarea
+ id: tasks
+ attributes:
+ label: "List of specific tasks or sub-tasks that need to be done to implement the user story."
+ description: "Outline the tasks necessary to implement the user story."
+ validations:
+ required: true
+
+ - type: markdown
+ attributes:
+ value: '**Dependencies:**'
+ - type: textarea
+ id: dependencies
+ attributes:
+ label: "List any dependencies that need to be resolved before starting or completing this user story."
+ description: "Specify any dependencies related to the user story."
+ validations:
+ required: false
+
+ - type: markdown
+ attributes:
+ value: '**Notes:**'
+ - type: textarea
+ id: notes
+ attributes:
+ label: "Any additional information or context that might be useful for the team."
+ description: "Provide any extra notes or context for the user story."
+ validations:
+ required: false
diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml
index 771209267..c308cd71a 100644
--- a/.github/workflows/deploy-demo.yml
+++ b/.github/workflows/deploy-demo.yml
@@ -18,7 +18,7 @@ jobs:
- name: Check for changes to Terraform
id: changed-terraform-files
- uses: tj-actions/changed-files@v1.1.2
+ uses: tj-actions/changed-files@v41.0.0
with:
files: |
terraform/demo
@@ -72,7 +72,7 @@ jobs:
- name: Check for changes to egress config
id: changed-egress-config
- uses: tj-actions/changed-files@v34
+ uses: tj-actions/changed-files@v41.0.0
with:
files: |
deploy-config/egress_proxy/notify-admin-demo.*.acl
diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml
index 0f696de36..3c1ab8b8a 100644
--- a/.github/workflows/deploy-prod.yml
+++ b/.github/workflows/deploy-prod.yml
@@ -18,7 +18,7 @@ jobs:
- name: Check for changes to Terraform
id: changed-terraform-files
- uses: tj-actions/changed-files@v1.1.2
+ uses: tj-actions/changed-files@v41.0.0
with:
files: |
terraform/production
@@ -73,7 +73,7 @@ jobs:
- name: Check for changes to egress config
id: changed-egress-config
- uses: tj-actions/changed-files@v34
+ uses: tj-actions/changed-files@v41.0.0
with:
files: |
deploy-config/egress_proxy/notify-admin-production.*.acl
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 8ef41c2a7..767acae72 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -23,7 +23,7 @@ jobs:
- name: Check for changes to Terraform
id: changed-terraform-files
- uses: tj-actions/changed-files@v1.1.2
+ uses: tj-actions/changed-files@v41.0.0
with:
files: |
terraform/staging
@@ -93,7 +93,7 @@ jobs:
- name: Check for changes to egress config
id: changed-egress-config
- uses: tj-actions/changed-files@v34
+ uses: tj-actions/changed-files@v41.0.0
with:
files: |
deploy-config/egress_proxy/notify-admin-staging.*.acl
diff --git a/app/__init__.py b/app/__init__.py
index 4e53bbb76..9ec056c2b 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -39,6 +39,7 @@ from app.asset_fingerprinter import asset_fingerprinter
from app.config import configs
from app.extensions import redis_client, zendesk_client
from app.formatters import (
+ convert_markdown_template,
convert_to_boolean,
format_auth_type,
format_billions,
@@ -88,6 +89,7 @@ from app.navigation import (
HeaderNavigation,
MainNavigation,
OrgNavigation,
+ SecondaryNavigation,
)
from app.notify_client import InviteTokenError
from app.notify_client.api_key_api_client import api_key_api_client
@@ -128,6 +130,7 @@ navigation = {
"main_navigation": MainNavigation(),
"header_navigation": HeaderNavigation(),
"org_navigation": OrgNavigation(),
+ "secondary_navigation": SecondaryNavigation(),
}
@@ -582,6 +585,7 @@ def add_template_filters(application):
format_mobile_network,
format_yes_no,
square_metres_to_square_miles,
+ convert_markdown_template,
]:
application.add_template_filter(fn)
diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss
index 341ee6d9a..06bff1ca7 100644
--- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss
+++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss
@@ -22,16 +22,26 @@ i.e.
@use "uswds-core" as *;
-.usa-header--extended .usa-logo {
- font-family: family("sans");
- margin: units(4) 0;
- @include at-media-max('mobile-lg') {
- margin: units(4) 0 units(4) units(2);
+.usa-header--extended {
+ .usa-logo {
+ font-family: family("sans");
+ margin: units(4) 0;
+ @include at-media-max('mobile-lg') {
+ margin: units(4) 0 units(4) units(2);
+ }
+ img {
+ @include at-media($theme-header-min-width) {
+ width: 80px;
+ height: 70px;
+ }
+ }
}
- img {
- @include at-media($theme-header-min-width) {
- width: 80px;
- height: 70px;
+ .usa-nav__secondary {
+ .usa-nav__link {
+ padding: 0;
+ &.usa-current {
+ text-decoration: underline;
+ }
}
}
}
@@ -44,15 +54,12 @@ i.e.
&> .usa-nav__primary-item > a {
font-size: size("body", 4);
}
- &> .usa-nav__primary-item:last-child {
- margin-left: auto;
- @include u-margin-right(-4);
- }
+ // &> .usa-nav__primary-item:last-child {
+ // margin-left: auto;
+ // @include u-margin-right(-4);
+ // }
}
-.usa-nav__primary
-
-
h1 {
font-weight: bold !important;
}
@@ -114,12 +121,46 @@ td.table-empty-message {
text-decoration: none !important;
}
+.button-flex-header {
+ flex-direction: column;
+ align-items: flex-start;
+ @include at-media(desktop) {
+ flex-direction: row;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ }
+}
+
.user-list-edit-link:active:before,
.user-list-edit-link:focus:before {
box-shadow: none;
border: 0;
}
+.user-list {
+ display: inline-flex;
+ flex-wrap: wrap;
+ gap: units(2);
+ .user-list-item {
+ @include at-media(desktop) {
+ flex: 1 1 calc(50% - units(2));
+ }
+ flex: 1 1 calc(100% - units(2));
+ border: 1px solid color('gray-cool-10');
+ padding: units(2);
+ .tick-cross-list-permissions {
+ margin: units(1) 0;
+ padding-left: units(2);
+ }
+ .hint {
+ display: block;
+ font-size: size("body", "sm");
+ font-weight: normal;
+ }
+ }
+}
+
.template-list-item-without-ancestors .template-list-folder:active,
.template-list-item-without-ancestors .template-list-folder:active::before,
.template-list-item-without-ancestors .template-list-folder:focus,
@@ -236,7 +277,7 @@ td.table-empty-message {
}
.navigation-service.usa-breadcrumb {
- -bottom: 0;
+ bottom: 0;
}
// Dashboard
diff --git a/app/content/get-started.md b/app/content/get-started.md
new file mode 100644
index 000000000..7846c0fe6
--- /dev/null
+++ b/app/content/get-started.md
@@ -0,0 +1,43 @@
+# Get started
+
+
+1. ## Check if Notify.gov is right for you
+
Read about our features , pricing and roadmap .
+
+
+2. ## Create an account
+ {% if not current_user.is_authenticated %}
+ Create an account for free and add your first Notify service. When you add a new service it will start in trial mode .
+ {% else %}
+ Create an account for free and add your first Notify service. When you add a new service, it will start in trial mode .
+ {% endif %}
+
+3. ## Write some messages
+ {% if True %}
+ Add message templates with examples of the content you plan to send. You can use our guidance to help you.
+ {% endif %}
+
+4. ## Set up your service
+ {% if not current_user.is_authenticated or not current_service %}
+ Review your settings to add message branding and sender information.
+ Add team members and check their permissions.
+ {% else %}
+ Review your settings to add message branding and sender information.
+ Add team members and check their permissions.
+ {% endif %}
+
+
+5. ## Start sending messages
+ {% if not current_user.is_authenticated or not current_service %}
+ When you’re ready to send messages to people outside your team, go to the Settings page and select Request to go live . We’ll approve your request within one working day.
+ {% else %}
+ You should request to go live when you’re ready to send messages to people outside your team. We’ll approve your request within one working day.
+ {% endif %}
+
+
+
+
diff --git a/app/formatters.py b/app/formatters.py
index 2442e70d0..86c187f55 100644
--- a/app/formatters.py
+++ b/app/formatters.py
@@ -1,3 +1,4 @@
+import os
import re
import unicodedata
import urllib
@@ -9,8 +10,11 @@ from numbers import Number
import ago
import dateutil
import humanize
+import markdown
import pytz
-from flask import Markup, url_for
+from bs4 import BeautifulSoup
+from flask import Markup, render_template_string, url_for
+from flask.helpers import get_root_path
from notifications_utils.field import Field
from notifications_utils.formatters import make_quotes_smart
from notifications_utils.formatters import nl2br as utils_nl2br
@@ -21,6 +25,41 @@ 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
+
+ for tag in tags:
+ element = tag[0]
+ class_name = tag[1]
+
+ soup = BeautifulSoup(new_html, "html.parser")
+
+ for xtag in soup.find_all(element):
+ xtag["class"] = class_name
+
+ new_html = str(soup)
+
+ return new_html
+
+
+def convert_markdown_template(mdf, test=False):
+ content_text = ""
+
+ if not test:
+ APP_ROOT = get_root_path("notifications-admin")
+ file = "app/content/" + mdf + ".md"
+ md_file = os.path.join(APP_ROOT, file)
+ with open(md_file) as f:
+ content_text = f.read()
+ else:
+ content_text = mdf
+
+ jn_render = render_template_string(content_text)
+ md_render = markdown.markdown(jn_render)
+
+ return md_render
+
+
def convert_to_boolean(value):
if isinstance(value, str):
if value.lower() in ["t", "true", "on", "yes", "1"]:
diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py
index 161b00eda..4bfdf5ed3 100644
--- a/app/main/views/dashboard.py
+++ b/app/main/views/dashboard.py
@@ -11,10 +11,12 @@ from werkzeug.utils import redirect
from app import (
billing_api_client,
current_service,
+ job_api_client,
+ notification_api_client,
service_api_client,
template_statistics_client,
)
-from app.formatters import format_date_numeric, format_datetime_numeric
+from app.formatters import format_date_numeric, format_datetime_numeric, get_time_left
from app.main import main
from app.statistics_utils import get_formatted_percentage
from app.utils import (
@@ -45,10 +47,55 @@ def service_dashboard(service_id):
if not current_user.has_permissions("view_activity"):
return redirect(url_for("main.choose_template", service_id=service_id))
+ download_availability = []
+
+ notifications = notification_api_client.get_notifications_for_service(
+ service_id=service_id,
+ )["notifications"]
+
+ notificaton_job_ids = [
+ notification["job"]["id"]
+ for notification in notifications
+ if "job" in notification
+ ]
+
+ jobs = []
+
+ for notificaton_job_id in notificaton_job_ids:
+ job_data = job_api_client.get_job(service_id, notificaton_job_id)["data"]
+ if job_data:
+ jobs.append(job_data)
+
+ service_data_retention_days = 7
+ download_availability = []
+ for job in jobs:
+ message_type = job.get("template_type")
+ if message_type is not None:
+ service_data_retention_days = current_service.get_days_of_retention(
+ message_type
+ )
+ time_left = get_time_left(job["created_at"])
+ download_link = (
+ url_for(
+ ".view_job_csv",
+ service_id=current_service.id,
+ job_id=job["id"],
+ ),
+ )
+ download_availability.append(
+ {
+ "job_id": job["id"],
+ "time_left": time_left,
+ "download_link": download_link,
+ }
+ )
return render_template(
"views/dashboard/dashboard.html",
updates_url=url_for(".service_dashboard_updates", service_id=service_id),
partials=get_dashboard_partials(service_id),
+ notifications=notifications,
+ download_availability=download_availability,
+ service_data_retention_days=service_data_retention_days,
)
diff --git a/app/main/views/index.py b/app/main/views/index.py
index 044543526..f4e3a67db 100644
--- a/app/main/views/index.py
+++ b/app/main/views/index.py
@@ -2,6 +2,7 @@ from flask import abort, redirect, render_template, request, url_for
from flask_login import current_user
from app import status_api_client
+from app.formatters import apply_html_class, convert_markdown_template
from app.main import main
from app.main.views.pricing import CURRENT_SMS_RATE
from app.main.views.sub_navigation_dictionaries import features_nav, using_notify_nav
@@ -137,9 +138,20 @@ def get_started_old():
@main.route("/using-notify/get-started")
@user_is_logged_in
def get_started():
+ markdown = convert_markdown_template("get-started")
+ html_style = apply_html_class(
+ [
+ ["ol", "usa-process-list"],
+ ["li", "usa-process-list__item padding-bottom-4 margin-top-2"],
+ ["h2", "usa-process-list__heading line-height-sans-3"],
+ ],
+ markdown,
+ )
+
return render_template(
"views/get-started.html",
navigation_links=using_notify_nav(),
+ content=html_style,
)
diff --git a/app/main/views/service_settings.py b/app/main/views/service_settings.py
index feb1e4388..1cac1410c 100644
--- a/app/main/views/service_settings.py
+++ b/app/main/views/service_settings.py
@@ -124,6 +124,14 @@ def service_switch_live(service_id):
if form.validate_on_submit():
current_service.update_status(live=form.enabled.data)
+ if form.enabled.data is True:
+ billing_api_client.create_or_update_free_sms_fragment_limit(
+ service_id, 250000
+ )
+ else:
+ billing_api_client.create_or_update_free_sms_fragment_limit(
+ service_id, 40000
+ )
return redirect(url_for(".service_settings", service_id=service_id))
return render_template(
diff --git a/app/navigation.py b/app/navigation.py
index e824d6de2..94f970540 100644
--- a/app/navigation.py
+++ b/app/navigation.py
@@ -87,39 +87,7 @@ class HeaderNavigation(Navigation):
"uploads",
"view_job",
"view_jobs",
- "confirm_edit_user_email",
- "confirm_edit_user_mobile_number",
- "edit_user_email",
- "edit_user_mobile_number",
- "edit_user_permissions",
- "invite_user",
- "manage_users",
- "remove_user_from_service",
"usage",
- "link_service_to_organization",
- "service_add_email_reply_to",
- "service_add_sms_sender",
- "service_confirm_delete_email_reply_to",
- "service_confirm_delete_sms_sender",
- "service_edit_email_reply_to",
- "service_edit_sms_sender",
- "service_email_reply_to",
- "service_name_change",
- "service_set_auth_type",
- "service_set_channel",
- "send_files_by_email_contact_details",
- "service_set_inbound_number",
- "service_set_inbound_sms",
- "service_set_international_sms",
- "service_set_reply_to_email",
- "service_set_sms_prefix",
- "service_verify_reply_to_address",
- "service_verify_reply_to_address_updates",
- "service_settings",
- "service_sms_senders",
- "set_free_sms_allowance",
- "set_message_limit",
- "set_rate_limit",
},
"pricing": {
"how_to_pay",
@@ -236,6 +204,21 @@ class MainNavigation(Navigation):
"usage": {
"usage",
},
+ "user-profile": {
+ "user_profile",
+ "user_profile_confirm_delete_mobile_number",
+ "user_profile_email",
+ "user_profile_email_authenticate",
+ "user_profile_email_confirm",
+ "user_profile_mobile_number",
+ "user_profile_mobile_number_authenticate",
+ "user_profile_mobile_number_confirm",
+ "user_profile_mobile_number_delete",
+ "user_profile_name",
+ "user_profile_password",
+ "user_profile_preferred_timezone",
+ "user_profile_disable_platform_admin_view",
+ },
"settings": {
"link_service_to_organization",
"service_add_email_reply_to",
@@ -297,6 +280,58 @@ class CaseworkNavigation(Navigation):
}
+class SecondaryNavigation(Navigation):
+ mapping = {
+ "settings": {
+ "link_service_to_organization",
+ "service_add_email_reply_to",
+ "service_add_sms_sender",
+ "service_confirm_delete_email_reply_to",
+ "service_confirm_delete_sms_sender",
+ "service_edit_email_reply_to",
+ "service_edit_sms_sender",
+ "service_email_reply_to",
+ "service_name_change",
+ "service_set_auth_type",
+ "service_set_channel",
+ "send_files_by_email_contact_details",
+ "service_set_inbound_number",
+ "service_set_inbound_sms",
+ "service_set_international_sms",
+ "service_set_reply_to_email",
+ "service_set_sms_prefix",
+ "service_verify_reply_to_address",
+ "service_verify_reply_to_address_updates",
+ "service_settings",
+ "service_sms_senders",
+ "set_free_sms_allowance",
+ "set_message_limit",
+ "set_rate_limit",
+ "confirm_edit_user_email",
+ "confirm_edit_user_mobile_number",
+ "edit_user_email",
+ "edit_user_mobile_number",
+ "edit_user_permissions",
+ "invite_user",
+ "manage_users",
+ "remove_user_from_service",
+ "user_profile",
+ "user_profile_confirm_delete_mobile_number",
+ "user_profile_email",
+ "user_profile_email_authenticate",
+ "user_profile_email_confirm",
+ "user_profile_mobile_number",
+ "user_profile_mobile_number_authenticate",
+ "user_profile_mobile_number_confirm",
+ "user_profile_mobile_number_delete",
+ "user_profile_name",
+ "user_profile_password",
+ "user_profile_preferred_timezone",
+ "user_profile_disable_platform_admin_view",
+ },
+ }
+
+
class OrgNavigation(Navigation):
mapping = {
"dashboard": {
diff --git a/app/templates/admin_template.html b/app/templates/admin_template.html
index 86482f829..c48f768ce 100644
--- a/app/templates/admin_template.html
+++ b/app/templates/admin_template.html
@@ -58,26 +58,37 @@
"text": "Features",
"active": header_navigation.is_selected('features')
},
- {
- "href": url_for('main.user_profile'),
- "text": current_user.name,
- "active": header_navigation.is_selected('user-profile')
- },
{
"href": url_for('main.platform_admin_splash_page'),
"text": "Platform admin",
"active": header_navigation.is_selected('platform-admin')
},
- {
- "href": url_for('main.sign_out'),
- "text": "Sign out"
- },
{
"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 = [
{
@@ -104,13 +115,29 @@
"href": url_for('main.user_profile'),
"text": "User profile",
"active": header_navigation.is_selected('user-profile')
- },
- {
- "href": url_for('main.sign_out'),
- "text": "Sign out"
}
] %}
- {% 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 %}
{% else %}
{# {% set navigation = [
@@ -142,6 +169,7 @@
"productName": "Notify",
"navigation": navigation,
"navigationClasses": "govuk-header__navigation--end",
+ "secondaryNavigation": secondaryNavigation,
"assetsPath": asset_path + "images"
}) }}
{% endblock %}
@@ -236,7 +264,7 @@
Please choose to extend your session or sign out. Your session will expire in 5 minutes or less.
-
You have been inactive for too long.
+
You have been inactive for too long.
Your session will expire in .
@@ -273,5 +301,3 @@
{% endblock %}
-
-
diff --git a/app/templates/components/components/header/template.njk b/app/templates/components/components/header/template.njk
index 1c817ea1c..6cc8cabd8 100644
--- a/app/templates/components/components/header/template.njk
+++ b/app/templates/components/components/header/template.njk
@@ -76,23 +76,18 @@
{% endif %}
{% endfor %}
-
+
{% if current_user.has_permissions('manage_api_keys') %}
{% endif %}
diff --git a/app/templates/settings_nav.html b/app/templates/settings_nav.html
new file mode 100644
index 000000000..d94c417e7
--- /dev/null
+++ b/app/templates/settings_nav.html
@@ -0,0 +1,16 @@
+{% if help %}
+{% include 'partials/tour.html' %}
+{% else %}
+
+
+
+{% endif %}
diff --git a/app/templates/settings_template.html b/app/templates/settings_template.html
new file mode 100644
index 000000000..4e956e811
--- /dev/null
+++ b/app/templates/settings_template.html
@@ -0,0 +1,34 @@
+{% extends "admin_template.html" %}
+
+{% block per_page_title %}
+ {% block service_page_title %}{% endblock %} – {{ current_service.name }}
+{% endblock %}
+
+{% block main %}
+
+
+ {% if help %}
+
+ {% else %}
+
+ {% endif %}
+ {% include "settings_nav.html" %}
+
+ {% if help %}
+
+ {% else %}
+
+ {% endif %}
+ {% block beforeContent %}
+ {% block backLink %}{% endblock %}
+ {% endblock %}
+
+ {% block content %}
+ {% include 'flash_messages.html' %}
+ {% block maincolumn_content %}{% endblock %}
+ {% endblock %}
+
+
+
+
+{% endblock %}
diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html
index 52a7694ac..ec528c2e3 100644
--- a/app/templates/views/dashboard/dashboard.html
+++ b/app/templates/views/dashboard/dashboard.html
@@ -1,6 +1,6 @@
{% extends "withnav_template.html" %}
-{% from "components/table.html" import list_table, field, right_aligned_field_heading, hidden_field_heading %}
+{% from "components/table.html" import list_table, field, text_field, link_field, right_aligned_field_heading, hidden_field_heading, row_heading, notification_status_field, notification_carrier_field, notification_carrier_message_field %}
{% from "components/ajax-block.html" import ajax_block %}
{% block service_page_title %}
@@ -29,10 +29,55 @@
{{ ajax_block(partials, updates_url, 'template-statistics') }}
+
Batched Jobs
+
+
+ {% call(item, row_number) list_table(
+ notifications,
+ caption="Batched Jobs",
+ caption_visible=False,
+ border_visible=True,
+ empty_message='No batched job messages found (messages are kept for {} days)'.format(service_data_retention_days)|safe,
+ field_headings=['Template Name','Date/Time', 'Download (CSV) Report'],
+ field_headings_visible=False
+ ) %}
+ {% if item.job.original_file_name and item.job.id %}
+ {% call row_heading() %}
+
{{ item.job.original_file_name|replace('.csv', '') if item.job.id else '' }}
+ {% endcall %}
+ {% call row_heading() %}
+ {{ item.status|format_notification_status_as_time(
+ item.created_at|format_datetime_short,
+ (item.updated_at or item.created_at)|format_datetime_short)
+ }}
+ {% endcall %}
+ {% call row_heading() %}
+ {% set availability = download_availability|selectattr('job_id', 'equalto', item.job.id)|first %}
+ {% if availability and availability.time_left != "Data no longer available" %}
+
{{ "Download" if item.job.original_file_name else '' }}
+
- {{ availability.time_left }}
+ {% elif availability %}
+
{{ availability.time_left }}
+ {% endif %}
+ {% endcall %}
+ {% endif %}
+ {% endcall %}
+
+
+
+ {% if show_pagination %}
+ {{ previous_next_navigation(prev_page, next_page) }}
+ {% elif next_page %}
+
+ Only showing the first 50 messages
+
+ {% endif %}
+
+
Usage
Daily
Across all services
-
+
Daily
diff --git a/app/templates/views/get-started.html b/app/templates/views/get-started.html
index d3c9363f9..e1da9961e 100644
--- a/app/templates/views/get-started.html
+++ b/app/templates/views/get-started.html
@@ -9,55 +9,6 @@
{% block content_column_content %}
-Get started
-
-
-
- Check if Notify.gov is right for you
- Read about our features , pricing and roadmap .
-
-
-
- Create an account
- {% if not current_user.is_authenticated %}
- Create an account for free and add your first Notify service. When you add a new service it will start in trial mode .
- {% else %}
- Create an account for free and add your first Notify service. When you add a new service, it will start in trial mode .
- {% endif %}
-
-
-
- Write some messages
- Add message templates with examples of the content you plan to send. You can use our guidance to help you.
-
-
-
- Set up your service
- {% if not current_user.is_authenticated or not current_service %}
- Review your settings to add message customization and sender information.
- Add team members and check their permissions.
- {% else %}
- Review your settings to add message customization and sender information.
- Add team members and check their permissions.
- {% endif %}
-
-
-
-
-
- Start sending messages
- {% if not current_user.is_authenticated or not current_service %}
- When you’re ready to send messages to people outside your team, go to the Settings page and select Request to go live . We’ll approve your request within one working day.
- {% else %}
- You should request to go live when you’re ready to send messages to people outside your team. We’ll approve your request within one working day.
- {% endif %}
-
-
-
-
+{{ content | safe }}
{% endblock %}
diff --git a/app/templates/views/manage-users.html b/app/templates/views/manage-users.html
index cab732a47..4b938e0d4 100644
--- a/app/templates/views/manage-users.html
+++ b/app/templates/views/manage-users.html
@@ -1,4 +1,4 @@
-{% extends "withnav_template.html" %}
+{% extends "settings_template.html" %}
{% from "components/tick-cross.html" import tick_cross %}
{% from "components/live-search.html" import live_search %}
{% from "components/components/button/macro.njk" import usaButton %}
@@ -9,8 +9,8 @@
{% block maincolumn_content %}
-
-
+
-
- -->
+
+ Notify.gov is a new shared service currently being piloted by the Public Benefits Studio, within Technology
+ Transformation Services at the General Services Administration.
+
+
+
+
+ {% endblock %}
+
{% endblock %}
diff --git a/app/templates/views/user-profile.html b/app/templates/views/user-profile.html
index 876a14280..d3e6ff7c8 100644
--- a/app/templates/views/user-profile.html
+++ b/app/templates/views/user-profile.html
@@ -1,4 +1,4 @@
-{% extends "withoutnav_template.html" %}
+{% extends "settings_template.html" %}
{% from "components/table.html" import list_table, row, field %}
{% from "components/table.html" import mapping_table, row, text_field, optional_text_field, edit_field, field, boolean_field with context %}
diff --git a/deploy-config/egress_proxy/notify-admin-staging.allow.acl b/deploy-config/egress_proxy/notify-admin-staging.allow.acl
index d329f111d..65c7931d2 100644
--- a/deploy-config/egress_proxy/notify-admin-staging.allow.acl
+++ b/deploy-config/egress_proxy/notify-admin-staging.allow.acl
@@ -1,2 +1,4 @@
gov-collector.newrelic.com
egress-proxy-notify-admin-staging.apps.internal
+idp.int.identitysandbox.gov
+secure.login.gov
diff --git a/docs/FCC RND Best Practices.pdf b/docs/FCC RND Best Practices.pdf
new file mode 100644
index 000000000..07cecf4b8
Binary files /dev/null and b/docs/FCC RND Best Practices.pdf differ
diff --git a/docs/sprint-goals.md b/docs/sprint-goals.md
index 598305e0e..17b405d7c 100644
--- a/docs/sprint-goals.md
+++ b/docs/sprint-goals.md
@@ -1,8 +1,29 @@
# Notify Sprint Goals Log
+## Sprint: A (1/3/24)
+2024 Sprints will be named after mythical creatures
-## Sprint: Y (12/6/23)
-(Sprint X was canceled due to Thanksgiving)
+| | Goals | Impact | Milestone(s) |
+|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|
+| Engineering | Continue [identifying](https://github.com/GSA/notifications-api/issues/656), [fixing](https://github.com/GSA/notifications-api/issues/645), and [displaying](https://github.com/GSA/notifications-admin/issues/964) various applicable message part limits, devise ways to [create better access to reports](https://github.com/GSA/notifications-admin/issues/855), implement Login.gov on staging environment, advance epic to [remove phone numbers from db](https://github.com/GSA/notifications-api/issues/667) | Better track annual and smart-retention limits, install required auth, make reports easier to find, expand message send volume for partners | Building Notify.gov |
+| UX | Spin up weekly [redesign syncs](https://github.com/GSA/notifications-admin/issues/1061), identify necessary dashboard metrics to visualize, improve [team member section](https://github.com/GSA/notifications-admin/issues/1031)| Improve UX | Building Notify.gov|
+| Security | Complete necessary control families | Aim to have package completed with enough time to allow for long assessment| Secure ATO |
+| Content | Finish making [editing content easier](https://github.com/GSA/notifications-admin/issues/976) with existing templates, finalize decision on terminology regarding [batch/campaign/job](https://github.com/GSA/notifications-admin/issues/1060), make content [more clear about limits](https://github.com/GSA/notifications-admin/issues/963), make content clearer by [adding `parts` where necessary](https://github.com/GSA/notifications-admin/issues/963) | Improve clarity of message parts for users, streamline content editing for the team| Building Notify.gov |
+| Ops | Assist in Tech to Gov process | |
+
+## Sprint: Zoe's Imperial Pigeon (12/21/23)
+Sprint velocity was very low due to holiday OOO
+
+| | Goals | Impact | Milestone(s) |
+|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|
+| Engineering | Spend time [identifying](https://github.com/GSA/notifications-api/issues/656), [fixing](https://github.com/GSA/notifications-api/issues/645), and [displaying](https://github.com/GSA/notifications-admin/issues/964) various applicable message part limits, devise ways to [create better access to reports](https://github.com/GSA/notifications-admin/issues/855), implement Login.gov on staging environment, advance epic to [remove phone numbers from db](https://github.com/GSA/notifications-api/issues/667) | Better track annual and smart-retention limits, install required auth, make reports easier to find, expand message send volume for partners | Building Notify.gov |
+| UX | [Re-think Notify IA](https://github.com/GSA/notifications-admin/issues/910), start implementing dashboard changes, improve [team member section](https://github.com/GSA/notifications-admin/issues/1031)| Improve UX | Building Notify.gov|
+| Security | Complete necessary control families | Aim to have package completed with enough time to allow for long assessment| Secure ATO |
+| Content | Make content [more clear about limits](https://github.com/GSA/notifications-admin/issues/963), make content clearer by [adding `parts` where necessary](https://github.com/GSA/notifications-admin/issues/963), test making [editing content easier](https://github.com/GSA/notifications-admin/issues/976) with existing templates | Improve clarity of message parts for users, streamline content editing for the team| Building Notify.gov |
+| Ops | Assist in Tech to Gov process | |
+
+## Sprint: Xingu Scale-Backed Antbird (12/6/23)
+(Sprint X was canceled due to Thanksgiving, so this is X & Y)
| | Goals | Impact | Milestone(s) |
|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|
diff --git a/poetry.lock b/poetry.lock
index dd52f645e..67f0828eb 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -82,33 +82,33 @@ lxml = ["lxml"]
[[package]]
name = "black"
-version = "23.12.0"
+version = "23.12.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
- {file = "black-23.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67f19562d367468ab59bd6c36a72b2c84bc2f16b59788690e02bbcb140a77175"},
- {file = "black-23.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbd75d9f28a7283b7426160ca21c5bd640ca7cd8ef6630b4754b6df9e2da8462"},
- {file = "black-23.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593596f699ca2dcbbbdfa59fcda7d8ad6604370c10228223cd6cf6ce1ce7ed7e"},
- {file = "black-23.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:12d5f10cce8dc27202e9a252acd1c9a426c83f95496c959406c96b785a92bb7d"},
- {file = "black-23.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e73c5e3d37e5a3513d16b33305713237a234396ae56769b839d7c40759b8a41c"},
- {file = "black-23.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba09cae1657c4f8a8c9ff6cfd4a6baaf915bb4ef7d03acffe6a2f6585fa1bd01"},
- {file = "black-23.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace64c1a349c162d6da3cef91e3b0e78c4fc596ffde9413efa0525456148873d"},
- {file = "black-23.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:72db37a2266b16d256b3ea88b9affcdd5c41a74db551ec3dd4609a59c17d25bf"},
- {file = "black-23.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fdf6f23c83078a6c8da2442f4d4eeb19c28ac2a6416da7671b72f0295c4a697b"},
- {file = "black-23.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39dda060b9b395a6b7bf9c5db28ac87b3c3f48d4fdff470fa8a94ab8271da47e"},
- {file = "black-23.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7231670266ca5191a76cb838185d9be59cfa4f5dd401b7c1c70b993c58f6b1b5"},
- {file = "black-23.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:193946e634e80bfb3aec41830f5d7431f8dd5b20d11d89be14b84a97c6b8bc75"},
- {file = "black-23.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcf91b01ddd91a2fed9a8006d7baa94ccefe7e518556470cf40213bd3d44bbbc"},
- {file = "black-23.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:996650a89fe5892714ea4ea87bc45e41a59a1e01675c42c433a35b490e5aa3f0"},
- {file = "black-23.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbff34c487239a63d86db0c9385b27cdd68b1bfa4e706aa74bb94a435403672"},
- {file = "black-23.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:97af22278043a6a1272daca10a6f4d36c04dfa77e61cbaaf4482e08f3640e9f0"},
- {file = "black-23.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ead25c273adfad1095a8ad32afdb8304933efba56e3c1d31b0fee4143a1e424a"},
- {file = "black-23.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c71048345bdbced456cddf1622832276d98a710196b842407840ae8055ade6ee"},
- {file = "black-23.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a832b6e00eef2c13b3239d514ea3b7d5cc3eaa03d0474eedcbbda59441ba5d"},
- {file = "black-23.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:6a82a711d13e61840fb11a6dfecc7287f2424f1ca34765e70c909a35ffa7fb95"},
- {file = "black-23.12.0-py3-none-any.whl", hash = "sha256:a7c07db8200b5315dc07e331dda4d889a56f6bf4db6a9c2a526fa3166a81614f"},
- {file = "black-23.12.0.tar.gz", hash = "sha256:330a327b422aca0634ecd115985c1c7fd7bdb5b5a2ef8aa9888a82e2ebe9437a"},
+ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
+ {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
+ {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
+ {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
+ {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
+ {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
+ {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
+ {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
+ {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
+ {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
+ {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
+ {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
+ {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
+ {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
+ {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
+ {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
+ {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
+ {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
+ {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
+ {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
+ {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
+ {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
]
[package.dependencies]
@@ -462,63 +462,63 @@ files = [
[[package]]
name = "coverage"
-version = "7.3.4"
+version = "7.4.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "coverage-7.3.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aff2bd3d585969cc4486bfc69655e862028b689404563e6b549e6a8244f226df"},
- {file = "coverage-7.3.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4353923f38d752ecfbd3f1f20bf7a3546993ae5ecd7c07fd2f25d40b4e54571"},
- {file = "coverage-7.3.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea473c37872f0159294f7073f3fa72f68b03a129799f3533b2bb44d5e9fa4f82"},
- {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5214362abf26e254d749fc0c18af4c57b532a4bfde1a057565616dd3b8d7cc94"},
- {file = "coverage-7.3.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f99b7d3f7a7adfa3d11e3a48d1a91bb65739555dd6a0d3fa68aa5852d962e5b1"},
- {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:74397a1263275bea9d736572d4cf338efaade2de9ff759f9c26bcdceb383bb49"},
- {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f154bd866318185ef5865ace5be3ac047b6d1cc0aeecf53bf83fe846f4384d5d"},
- {file = "coverage-7.3.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e0d84099ea7cba9ff467f9c6f747e3fc3906e2aadac1ce7b41add72e8d0a3712"},
- {file = "coverage-7.3.4-cp310-cp310-win32.whl", hash = "sha256:3f477fb8a56e0c603587b8278d9dbd32e54bcc2922d62405f65574bd76eba78a"},
- {file = "coverage-7.3.4-cp310-cp310-win_amd64.whl", hash = "sha256:c75738ce13d257efbb6633a049fb2ed8e87e2e6c2e906c52d1093a4d08d67c6b"},
- {file = "coverage-7.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:997aa14b3e014339d8101b9886063c5d06238848905d9ad6c6eabe533440a9a7"},
- {file = "coverage-7.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8a9c5bc5db3eb4cd55ecb8397d8e9b70247904f8eca718cc53c12dcc98e59fc8"},
- {file = "coverage-7.3.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27ee94f088397d1feea3cb524e4313ff0410ead7d968029ecc4bc5a7e1d34fbf"},
- {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ce03e25e18dd9bf44723e83bc202114817f3367789052dc9e5b5c79f40cf59d"},
- {file = "coverage-7.3.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85072e99474d894e5df582faec04abe137b28972d5e466999bc64fc37f564a03"},
- {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a877810ef918d0d345b783fc569608804f3ed2507bf32f14f652e4eaf5d8f8d0"},
- {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9ac17b94ab4ca66cf803f2b22d47e392f0977f9da838bf71d1f0db6c32893cb9"},
- {file = "coverage-7.3.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:36d75ef2acab74dc948d0b537ef021306796da551e8ac8b467810911000af66a"},
- {file = "coverage-7.3.4-cp311-cp311-win32.whl", hash = "sha256:47ee56c2cd445ea35a8cc3ad5c8134cb9bece3a5cb50bb8265514208d0a65928"},
- {file = "coverage-7.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:11ab62d0ce5d9324915726f611f511a761efcca970bd49d876cf831b4de65be5"},
- {file = "coverage-7.3.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:33e63c578f4acce1b6cd292a66bc30164495010f1091d4b7529d014845cd9bee"},
- {file = "coverage-7.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:782693b817218169bfeb9b9ba7f4a9f242764e180ac9589b45112571f32a0ba6"},
- {file = "coverage-7.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4277ddaad9293454da19121c59f2d850f16bcb27f71f89a5c4836906eb35ef"},
- {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d892a19ae24b9801771a5a989fb3e850bd1ad2e2b6e83e949c65e8f37bc67a1"},
- {file = "coverage-7.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3024ec1b3a221bd10b5d87337d0373c2bcaf7afd86d42081afe39b3e1820323b"},
- {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1c3e9d2bbd6f3f79cfecd6f20854f4dc0c6e0ec317df2b265266d0dc06535f1"},
- {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e91029d7f151d8bf5ab7d8bfe2c3dbefd239759d642b211a677bc0709c9fdb96"},
- {file = "coverage-7.3.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6879fe41c60080aa4bb59703a526c54e0412b77e649a0d06a61782ecf0853ee1"},
- {file = "coverage-7.3.4-cp312-cp312-win32.whl", hash = "sha256:fd2f8a641f8f193968afdc8fd1697e602e199931012b574194052d132a79be13"},
- {file = "coverage-7.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:d1d0ce6c6947a3a4aa5479bebceff2c807b9f3b529b637e2b33dea4468d75fc7"},
- {file = "coverage-7.3.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:36797b3625d1da885b369bdaaa3b0d9fb8865caed3c2b8230afaa6005434aa2f"},
- {file = "coverage-7.3.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfed0ec4b419fbc807dec417c401499ea869436910e1ca524cfb4f81cf3f60e7"},
- {file = "coverage-7.3.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f97ff5a9fc2ca47f3383482858dd2cb8ddbf7514427eecf5aa5f7992d0571429"},
- {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b6c6b35aa49defaebf4526729bd5238bc36fe3ef1a417d9839e1d96ee1e4c"},
- {file = "coverage-7.3.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8e258dcc335055ab59fe79f1dec217d9fb0cdace103d6b5c6df6b75915e7959"},
- {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a02ac7c51819702b384fea5ee033a7c202f732a2a2f1fe6c41e3d4019828c8d3"},
- {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b710869a15b8caf02e31d16487a931dbe78335462a122c8603bb9bd401ff6fb2"},
- {file = "coverage-7.3.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c6a23ae9348a7a92e7f750f9b7e828448e428e99c24616dec93a0720342f241d"},
- {file = "coverage-7.3.4-cp38-cp38-win32.whl", hash = "sha256:758ebaf74578b73f727acc4e8ab4b16ab6f22a5ffd7dd254e5946aba42a4ce76"},
- {file = "coverage-7.3.4-cp38-cp38-win_amd64.whl", hash = "sha256:309ed6a559bc942b7cc721f2976326efbfe81fc2b8f601c722bff927328507dc"},
- {file = "coverage-7.3.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aefbb29dc56317a4fcb2f3857d5bce9b881038ed7e5aa5d3bcab25bd23f57328"},
- {file = "coverage-7.3.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:183c16173a70caf92e2dfcfe7c7a576de6fa9edc4119b8e13f91db7ca33a7923"},
- {file = "coverage-7.3.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a4184dcbe4f98d86470273e758f1d24191ca095412e4335ff27b417291f5964"},
- {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93698ac0995516ccdca55342599a1463ed2e2d8942316da31686d4d614597ef9"},
- {file = "coverage-7.3.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb220b3596358a86361139edce40d97da7458412d412e1e10c8e1970ee8c09ab"},
- {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5b14abde6f8d969e6b9dd8c7a013d9a2b52af1235fe7bebef25ad5c8f47fa18"},
- {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:610afaf929dc0e09a5eef6981edb6a57a46b7eceff151947b836d869d6d567c1"},
- {file = "coverage-7.3.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed790728fb71e6b8247bd28e77e99d0c276dff952389b5388169b8ca7b1c28"},
- {file = "coverage-7.3.4-cp39-cp39-win32.whl", hash = "sha256:c15fdfb141fcf6a900e68bfa35689e1256a670db32b96e7a931cab4a0e1600e5"},
- {file = "coverage-7.3.4-cp39-cp39-win_amd64.whl", hash = "sha256:38d0b307c4d99a7aca4e00cad4311b7c51b7ac38fb7dea2abe0d182dd4008e05"},
- {file = "coverage-7.3.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b1e0f25ae99cf247abfb3f0fac7ae25739e4cd96bf1afa3537827c576b4847e5"},
- {file = "coverage-7.3.4.tar.gz", hash = "sha256:020d56d2da5bc22a0e00a5b0d54597ee91ad72446fa4cf1b97c35022f6b6dbf0"},
+ {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"},
+ {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"},
+ {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"},
+ {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"},
+ {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"},
+ {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"},
+ {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"},
+ {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"},
+ {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"},
+ {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"},
+ {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"},
+ {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"},
+ {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"},
+ {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"},
+ {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"},
+ {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"},
+ {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"},
+ {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"},
+ {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"},
+ {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"},
+ {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"},
+ {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"},
]
[package.extras]
@@ -571,21 +571,26 @@ test-randomorder = ["pytest-randomly"]
[[package]]
name = "cyclonedx-python-lib"
-version = "4.2.3"
-description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files."
+version = "6.3.0"
+description = "Python library for CycloneDX"
optional = false
-python-versions = ">=3.7,<4.0"
+python-versions = ">=3.8,<4.0"
files = [
- {file = "cyclonedx_python_lib-4.2.3-py3-none-any.whl", hash = "sha256:e9b923af525b6acf7bab917a35360b4b562b85dc15fde9eaa500828949adf73a"},
- {file = "cyclonedx_python_lib-4.2.3.tar.gz", hash = "sha256:904068b55d1665f0ea96f38307603cc14f95c3b421f1687fc2411326aefde3a6"},
+ {file = "cyclonedx_python_lib-6.3.0-py3-none-any.whl", hash = "sha256:0e73c1036c2f7fc67adc28aef807e6b44340ea70202aab197fb06b20ea165de8"},
+ {file = "cyclonedx_python_lib-6.3.0.tar.gz", hash = "sha256:82f2489de3c0cadad5af1ad7fa6b6a185f985746370245d38769699c734533c6"},
]
[package.dependencies]
license-expression = ">=30,<31"
packageurl-python = ">=0.11"
-py-serializable = ">=0.11.1,<0.12.0"
+py-serializable = ">=0.16,<0.18"
sortedcontainers = ">=2.4.0,<3.0.0"
+[package.extras]
+json-validation = ["jsonschema[format] (>=4.18,<5.0)"]
+validation = ["jsonschema[format] (>=4.18,<5.0)", "lxml (>=4,<6)"]
+xml-validation = ["lxml (>=4,<6)"]
+
[[package]]
name = "defusedxml"
version = "0.7.1"
@@ -923,20 +928,20 @@ smmap = ">=3.0.1,<6"
[[package]]
name = "gitpython"
-version = "3.1.40"
+version = "3.1.41"
description = "GitPython is a Python library used to interact with Git repositories"
optional = false
python-versions = ">=3.7"
files = [
- {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"},
- {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"},
+ {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"},
+ {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"},
]
[package.dependencies]
gitdb = ">=4.0.1,<5"
[package.extras]
-test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"]
+test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"]
[[package]]
name = "govuk-bank-holidays"
@@ -1182,13 +1187,13 @@ files = [
[[package]]
name = "jinja2"
-version = "3.1.2"
+version = "3.1.3"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
files = [
- {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
- {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+ {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
+ {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
]
[package.dependencies]
@@ -1382,6 +1387,24 @@ six = "*"
[package.extras]
restructuredtext = ["rst2ansi"]
+[[package]]
+name = "markdown"
+version = "3.5.2"
+description = "Python implementation of John Gruber's Markdown."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"},
+ {file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"},
+]
+
+[package.dependencies]
+importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
+
+[package.extras]
+docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
+testing = ["coverage", "pyyaml"]
+
[[package]]
name = "markdown-it-py"
version = "3.0.0"
@@ -1510,13 +1533,13 @@ files = [
[[package]]
name = "moto"
-version = "4.2.12"
+version = "4.2.13"
description = ""
optional = false
python-versions = ">=3.7"
files = [
- {file = "moto-4.2.12-py2.py3-none-any.whl", hash = "sha256:bdcad46e066a55b7d308a786e5dca863b3cba04c6239c6974135a48d1198b3ab"},
- {file = "moto-4.2.12.tar.gz", hash = "sha256:7c4d37f47becb4a0526b64df54484e988c10fde26861fc3b5c065bc78800cb59"},
+ {file = "moto-4.2.13-py2.py3-none-any.whl", hash = "sha256:93e0fd13b624bd79115494f833308c3641b2be0fc9f4f18aa9264aa01f6168e0"},
+ {file = "moto-4.2.13.tar.gz", hash = "sha256:01aef6a489a725c8d725bd3dc6f70ff1bedaee3e2641752e4b471ff0ede4b4d7"},
]
[package.dependencies]
@@ -1635,26 +1658,40 @@ files = [
[[package]]
name = "newrelic"
-version = "9.3.0"
+version = "9.5.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.*"
files = [
- {file = "newrelic-9.3.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f25980a8c86bda75344b5b22edd5d6ad41777776e1ed8a495eb6e38e9813b02c"},
- {file = "newrelic-9.3.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:adefb6620c5a5d75b4bf3ec565cc4d91abcb5cc4e5569f5f82ab29fa3d5aa2d9"},
- {file = "newrelic-9.3.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:27056ab8a3cf39787fc1f93f55243749dd25786f65b15032b6fbb3e8534f4c2a"},
- {file = "newrelic-9.3.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:663fa1c074661f93abf681c8f6028de64744c67f004b722835de1372b6bc4d19"},
- {file = "newrelic-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83346c8f0bcb8f07f74c88f6073e4d44a2e2b3eeec5b2ebe8c450ae695d02b88"},
- {file = "newrelic-9.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2187078d7b0054b30f39dbf891cb2caa71a7046f6d0258fb8c0fcfce70777774"},
- {file = "newrelic-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0f5edd4eba3d62742b3b0730bb4f826be015dd7fbb9c455b01c410421661a2"},
- {file = "newrelic-9.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8577a0f733174bee70a147f71aa061fb44a593a1be841feffe12dff480c6e02e"},
- {file = "newrelic-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:234594655ac0fbe938d34ce5d5d38549d0f5cc11d0552170903ad09574bb4499"},
- {file = "newrelic-9.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce38949404e974b566b21487394f5ea36a1fb80ba36cc4a6e8fb968d2e150ab"},
- {file = "newrelic-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a750ffed8aedacdafcb548b3d3f45630a96862db630c9f72520ebbfe91e4e9e0"},
- {file = "newrelic-9.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01d0f0c22b1290dbd2756ef120cfbe154179aae35e1dfc579f8bfd574066105"},
- {file = "newrelic-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e860c6eacfdef879f23fbbf7d76d8bbb90b725a1c422f62439c6edfceebc21"},
- {file = "newrelic-9.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4caad3017cc978f3130fe2f3933f233c214a4850a595458b733282b3b7f7e886"},
- {file = "newrelic-9.3.0.tar.gz", hash = "sha256:c2dd685527433f6b6fbffe58f83852b46c24b9713ebb8ee7af647e04c2de3ee4"},
+ {file = "newrelic-9.5.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:87c5ea1db9b2f573c7fb0961b7a856a91db0c9f130303de1839c542586b8dc87"},
+ {file = "newrelic-9.5.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8c62c07a69b86c54d3b9ce187551f36683ad8b70b6301273e7c1e89b121b8245"},
+ {file = "newrelic-9.5.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:847dd07961adec25c4c1128e8666f72ba0ab03640a44a65bcf25ffebd6fc2a75"},
+ {file = "newrelic-9.5.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:12be024a5dfd1667d05d7cce968189e2490b6f6d24f194695365607b2340be20"},
+ {file = "newrelic-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c381d300d904e2a5eb60953a29a729dbd4d8213f6dd629cecaa3033db70a9060"},
+ {file = "newrelic-9.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2e4ccd5fe97496806fe0ea1acb8b15791c1f012aa44fb047fc8016bcfb80ab9"},
+ {file = "newrelic-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:14ef5e94d490315a73cf4bea4cd0186b2ec68009e90ab2c16a0b501942fa8629"},
+ {file = "newrelic-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:306e54751fe1eba61884b0ed63e9583711b37e24db9c7b36725317c21aa609f5"},
+ {file = "newrelic-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ac2b3603cc781ef58e51a95b0b58220feb3e322c282c758c3bf5430398a47bc"},
+ {file = "newrelic-9.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b0a3fbbd6ef1dca0d2434c8ddd15d945fef2cd343d09b3a352b8de825d2e83"},
+ {file = "newrelic-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ab045fe37dcfd9c1fefc84f72373c54abd546d7517909fabd6920ea1d87c3f7f"},
+ {file = "newrelic-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e54492c5f70cf0951ffc97ddd71b3cf64d68b4d670012d7a7d5ff3dc19417dab"},
+ {file = "newrelic-9.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92968b7c6951f3ee0fe85a465ae79d448e587a3d10bf71d85b2cab09aa319776"},
+ {file = "newrelic-9.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847d75a08ca3ba356b6d6317618243e546dc7725251a453d4730ac6e9f043b71"},
+ {file = "newrelic-9.5.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec0fe958c5ed52cd8b496339df8661badbd24bdc290dea8744de94da705ccef0"},
+ {file = "newrelic-9.5.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a2af10be7893f60588e2103b2f5ff616baf8132ecce90d9a8c7bfb832da08748"},
+ {file = "newrelic-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2556ae2b9c13127dc46a6326c1ea682eae2c2b64b6873cfd4d5e177c7e0ce58a"},
+ {file = "newrelic-9.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e9818646a7a72332e5ab3853277187af5b0d51312c51585a5581453fbdc1508"},
+ {file = "newrelic-9.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b370179f644c0265e4467221e895dccc75440388c5cce8d6a89d150c6b265021"},
+ {file = "newrelic-9.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c514f82942f7759bc4a360d908c4be37b8764a3ff1589ba3ad8b4baf201bad36"},
+ {file = "newrelic-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15d4c2dd584c7604a03b2c73464ed3054cd8f0a513ed25832f1a3af4eb6e80ab"},
+ {file = "newrelic-9.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2af79b43e3244ce7775eb7a0bf2e8fe2cb1624f6eb1be03cb6c35f9e8a5be77"},
+ {file = "newrelic-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1dab3ef8baffebcf3f088a4131d93f3ac0e1fc08fa6045927c6bd2746b4decf4"},
+ {file = "newrelic-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:78ca19f47b2bc38e18516c2a7c485eeac6979b271a1a5a4ac3b950581ca8a230"},
+ {file = "newrelic-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63314277c983cd412b87dfa3ed101b88c40ccab683a98046d76bf44a33cb7637"},
+ {file = "newrelic-9.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68917968adba4abde385be1c23d8fdb13714968883c3a3d1a83fb676ea614245"},
+ {file = "newrelic-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fb49e3a2e8ab9b509c8d276382f7ce0f6b4e51758b0edec38131ae583532b121"},
+ {file = "newrelic-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4638e6c28ebf2b5a8a860ae33105a1816425f6ece0113df626c88e5626b22f41"},
+ {file = "newrelic-9.5.0.tar.gz", hash = "sha256:2442449c4798989d92f527a82e60c446821c97896d739492b607bf6d4018ba1b"},
]
[package.extras]
@@ -1676,12 +1713,12 @@ setuptools = "*"
[[package]]
name = "notifications-python-client"
-version = "8.1.0"
+version = "8.2.0"
description = "Python API client for GOV.UK Notify."
optional = false
python-versions = ">=3.7"
files = [
- {file = "notifications_python_client-8.1.0-py3-none-any.whl", hash = "sha256:8aec1f7a4ba592fd699eae899df8ccca9ccafb614f47826c07b318ca903c9c13"},
+ {file = "notifications_python_client-8.2.0-py3-none-any.whl", hash = "sha256:8cd8bd01ae603a972a5413c430ca42b16f6481f37d98406c3e9b68c1c192f59e"},
]
[package.dependencies]
@@ -1691,7 +1728,7 @@ requests = ">=2.0.0"
[[package]]
name = "notifications-utils"
-version = "0.2.4"
+version = "0.2.6"
description = ""
optional = false
python-versions = ">=3.9,<3.12"
@@ -1716,7 +1753,7 @@ geojson = "^3.0.1"
govuk-bank-holidays = "^0.13"
idna = "^3.4"
itsdangerous = "^2.1.2"
-jinja2 = "^3.1.2"
+jinja2 = "^3.1.3"
jmespath = "^1.0.1"
markupsafe = "^2.1.2"
mistune = "==0.8.4"
@@ -1743,7 +1780,7 @@ werkzeug = "^3.0.1"
type = "git"
url = "https://github.com/GSA/notifications-utils.git"
reference = "HEAD"
-resolved_reference = "bd604dc32ea80b5d8a1157b09653e4bd4a755a6e"
+resolved_reference = "f183d120a8b86e655405694adde1f0d95e4e5a51"
[[package]]
name = "numpy"
@@ -1902,18 +1939,18 @@ pip = "*"
[[package]]
name = "pip-audit"
-version = "2.6.2"
+version = "2.7.0"
description = "A tool for scanning Python environments for known vulnerabilities"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pip_audit-2.6.2-py3-none-any.whl", hash = "sha256:ac3a4b6e977ef2c574aa8d19a5d71d12201bdb65bba2d67d9df49f53f0be5e7d"},
- {file = "pip_audit-2.6.2.tar.gz", hash = "sha256:0bbd023a199a104b29f949f063a872d41113b5a9048285666820fa35a76a7794"},
+ {file = "pip_audit-2.7.0-py3-none-any.whl", hash = "sha256:83e039740653eb9ef1a78b1540ed441600cd88a560588ba2c0a169180685a522"},
+ {file = "pip_audit-2.7.0.tar.gz", hash = "sha256:67740c5b1d5d967a258c3dfefc46f9713a2819c48062505ddf4b29de101c2b75"},
]
[package.dependencies]
CacheControl = {version = ">=0.13.0", extras = ["filecache"]}
-cyclonedx-python-lib = ">=4,<6"
+cyclonedx-python-lib = ">=5,<7"
html5lib = ">=1.1"
packaging = ">=23.0.0"
pip-api = ">=0.0.28"
@@ -1925,7 +1962,7 @@ toml = ">=0.10"
[package.extras]
dev = ["build", "bump (>=1.3.2)", "pip-audit[doc,lint,test]"]
doc = ["pdoc"]
-lint = ["interrogate", "mypy", "ruff (<0.1.9)", "types-html5lib", "types-requests", "types-toml"]
+lint = ["interrogate", "mypy", "ruff (<0.1.12)", "types-html5lib", "types-requests", "types-toml"]
test = ["coverage[toml] (>=7.0,!=7.3.3,<8.0)", "pretend", "pytest", "pytest-cov"]
[[package]]
@@ -2017,13 +2054,13 @@ virtualenv = ">=20.10.0"
[[package]]
name = "py-serializable"
-version = "0.11.1"
+version = "0.17.1"
description = "Library for serializing and deserializing Python Objects to and from JSON and XML."
optional = false
python-versions = ">=3.7,<4.0"
files = [
- {file = "py-serializable-0.11.1.tar.gz", hash = "sha256:ba0e1287b9e4f645a5334f1913abd8e647e7250209f84f55dce3909498a6f586"},
- {file = "py_serializable-0.11.1-py3-none-any.whl", hash = "sha256:79e21f0672822e6200b15f45ce9f636e8126466f62dbd7d488c67313c72b5c3e"},
+ {file = "py-serializable-0.17.1.tar.gz", hash = "sha256:875bb9c01df77f563dfcd1e75bb4244b5596083d3aad4ccd3fb63e1f5a9d3e5f"},
+ {file = "py_serializable-0.17.1-py3-none-any.whl", hash = "sha256:389c2254d912bec3a44acdac667c947d73c59325050d5ae66386e1ed7108a45a"},
]
[package.dependencies]
@@ -2268,13 +2305,13 @@ certifi = "*"
[[package]]
name = "pytest"
-version = "7.4.3"
+version = "7.4.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
- {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
+ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
+ {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
]
[package.dependencies]
@@ -3018,13 +3055,13 @@ watchdog = ["watchdog (>=2.3)"]
[[package]]
name = "wtforms"
-version = "3.1.1"
+version = "3.1.2"
description = "Form validation and rendering for Python web development."
optional = false
python-versions = ">=3.8"
files = [
- {file = "wtforms-3.1.1-py3-none-any.whl", hash = "sha256:ae7c54b29806c70f7bce8eb9f24afceb10ca5c32af3d9f04f74d2f66ccc5c7e0"},
- {file = "wtforms-3.1.1.tar.gz", hash = "sha256:5e51df8af9a60f6beead75efa10975e97768825a82146a65c7cbf5b915990620"},
+ {file = "wtforms-3.1.2-py3-none-any.whl", hash = "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07"},
+ {file = "wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9"},
]
[package.dependencies]
@@ -3089,4 +3126,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.12"
-content-hash = "f06451d8cf0d8f4d59b67f06d2ede8153a19055403a81b90afc700dcf60d139b"
+content-hash = "6f9697c8f58f1d17b4bc28f0ed908c4c1a327a2f4eeafbdf7374692408e9d682"
diff --git a/pyproject.toml b/pyproject.toml
index 94da0306f..881eb9163 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,6 +8,7 @@ readme = "README.md"
[tool.poetry.dependencies]
python = ">=3.9,<3.12"
ago = "~=0.0.95"
+beautifulsoup4 = "^4.12.2"
blinker = "~=1.7"
exceptiongroup = "==1.2.0"
flask = "~=2.3"
@@ -22,7 +23,7 @@ humanize = "~=4.9"
itsdangerous = "~=2.1"
jinja2 = "~=3.1"
newrelic = "*"
-notifications-python-client = "==8.1.0"
+notifications-python-client = "==8.2.0"
notifications-utils = {git = "https://github.com/GSA/notifications-utils.git"}
pyexcel = "==0.7.0"
pyexcel-io = "==0.6.6"
@@ -36,12 +37,12 @@ pytz = "==2023.3.post1"
rtreelib = "==0.2.0"
werkzeug = "^3.0.1"
wtforms = "~=3.1"
+markdown = "^3.5.2"
[tool.poetry.group.dev.dependencies]
bandit = "*"
-beautifulsoup4 = "^4.12.2"
-black = "^23.12.0"
+black = "^23.12.1"
coverage = "*"
freezegun = "^1.4.0"
flake8 = "^6.1.0"
@@ -53,7 +54,7 @@ jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
moto = "^4.2"
pip-audit = "*"
pre-commit = "^3.6.0"
-pytest = "^7.4.3"
+pytest = "^7.4.4"
pytest-env = "^1.1.3"
pytest-mock = "^3.12.0"
pytest-playwright = "^0.4.3"
diff --git a/tests/app/main/test_formatters.py b/tests/app/main/test_formatters.py
index 46e76da0d..b9fd7f975 100644
--- a/tests/app/main/test_formatters.py
+++ b/tests/app/main/test_formatters.py
@@ -2,10 +2,12 @@ from datetime import datetime
from functools import partial
import pytest
-from flask import url_for
+from flask import Flask, url_for
from freezegun import freeze_time
from app.formatters import (
+ apply_html_class,
+ convert_markdown_template,
email_safe,
format_datetime_relative,
format_delta,
@@ -166,3 +168,29 @@ def test_email_safe_return_dot_separated_email_domain(service_name, safe_email):
def test_format_delta():
naive_now_utc = datetime.utcnow().isoformat()
assert format_delta(naive_now_utc) == "just now"
+
+
+@pytest.mark.usefixtures("fake_jinja_template")
+def test_jinja_format(fake_jinja_template):
+ app = Flask("app")
+ with app.app_context():
+ assert (
+ convert_markdown_template(fake_jinja_template, test=True) == "True
"
+ )
+
+
+@pytest.mark.usefixtures("fake_markdown_file")
+def test_markdown_format(fake_markdown_file):
+ app = Flask("app")
+ with app.app_context():
+ assert (
+ convert_markdown_template(fake_markdown_file, test=True) == "Test "
+ )
+
+
+@pytest.mark.usefixtures("fake_soup_template")
+def test_soup_format(fake_soup_template):
+ assert (
+ apply_html_class([["h1", "testClass"]], fake_soup_template)
+ == 'Test '
+ )
diff --git a/tests/app/main/test_permissions.py b/tests/app/main/test_permissions.py
index fea2c2fb5..3ad521ae2 100644
--- a/tests/app/main/test_permissions.py
+++ b/tests/app/main/test_permissions.py
@@ -125,7 +125,6 @@ def test_service_navigation_for_org_user(
(
"Send messages",
"Sent messages",
- "Team members",
),
403,
),
@@ -134,7 +133,6 @@ def test_service_navigation_for_org_user(
(
"Send messages",
"Sent messages",
- "Team members",
"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 8b94ebe64..dae5c712a 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 " "Team members"
+ "Test Service Switch service " "Send messages " "Dashboard " "Usage"
) # TODO: set sidebar variables in common test module
page = client_request.get(
diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py
index 696a3ea83..76d3a6b79 100644
--- a/tests/app/main/views/test_accept_invite.py
+++ b/tests/app/main/views/test_accept_invite.py
@@ -14,6 +14,108 @@ from tests.conftest import (
normalize_spaces,
)
+FAKE_ONE_OFF_NOTIFICATION = {
+ "links": {},
+ "notifications": [
+ {
+ "api_key": None,
+ "billable_units": 0,
+ "carrier": None,
+ "client_reference": None,
+ "created_at": "2023-12-14T20:35:55+00:00",
+ "created_by": {
+ "email_address": "grsrbsrgsrf@fake.gov",
+ "id": "de059e0a-42e5-48bb-939e-4f76804ab739",
+ "name": "grsrbsrgsrf",
+ },
+ "document_download_count": None,
+ "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81",
+ "international": False,
+ "job": {
+ "id": "55b242b5-9f62-4271-aff7-039e9c320578",
+ "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv",
+ },
+ "job_row_number": 0,
+ "key_name": None,
+ "key_type": "normal",
+ "normalised_to": "+16615555555",
+ "notification_type": "sms",
+ "personalisation": {
+ "dayofweek": "2",
+ "favecolor": "3",
+ "phonenumber": "+16615555555",
+ },
+ "phone_prefix": "1",
+ "provider_response": None,
+ "rate_multiplier": 1.0,
+ "reference": None,
+ "reply_to_text": "development",
+ "sent_at": None,
+ "sent_by": None,
+ "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1",
+ "status": "created",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+16615555555",
+ "updated_at": None,
+ }
+ ],
+ "page_size": 50,
+ "total": 1,
+}
+
+MOCK_ONE_OFF_JOB = {
+ "data": {
+ "api_key": "mocked_api_key",
+ "billable_units": 1,
+ "carrier": "mocked_carrier",
+ "client_reference": "mocked_client_reference",
+ "created_at": "2024-01-04T20:43:52+00:00",
+ "created_by": {
+ "email_address": "mocked_email@example.com",
+ "id": "mocked_user_id",
+ "name": "mocked_user",
+ },
+ "document_download_count": None,
+ "id": "mocked_notification_id",
+ "international": False,
+ "job": {"id": "mocked_job_id", "original_file_name": "mocked_file.txt"},
+ "job_row_number": 0,
+ "key_name": "mocked_key_name",
+ "key_type": "normal",
+ "normalised_to": "+12133166548",
+ "notification_type": "sms",
+ "personalisation": {"phonenumber": "+12133166548"},
+ "phone_prefix": "1",
+ "provider_response": "mocked_provider_response",
+ "rate_multiplier": 1.0,
+ "reference": "mocked_reference",
+ "reply_to_text": "mocked_reply_text",
+ "sent_at": "2024-01-04T20:43:53+00:00",
+ "sent_by": "mocked_sender",
+ "service": "mocked_service_id",
+ "status": "sending",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+12133166548",
+ "updated_at": "2024-01-04T20:43:53+00:00",
+ }
+}
+
@pytest.fixture()
def _mock_no_users_for_service(mocker):
@@ -207,7 +309,11 @@ def test_accepting_invite_removes_invite_from_session(
sample_invite["email_address"] = user["email_address"]
client_request.login(user)
-
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.accept_invite",
token="thisisnotarealtoken",
@@ -619,6 +725,11 @@ def test_new_invited_user_verifies_and_added_to_service(
token="thisisnotarealtoken",
_expected_redirect=url_for("main.register_from_invite"),
)
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
# get redirected to register from invite
data = {
diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py
index 010b53d5d..b15e55a48 100644
--- a/tests/app/main/views/test_dashboard.py
+++ b/tests/app/main/views/test_dashboard.py
@@ -28,6 +28,108 @@ from tests.conftest import (
normalize_spaces,
)
+FAKE_ONE_OFF_NOTIFICATION = {
+ "links": {},
+ "notifications": [
+ {
+ "api_key": None,
+ "billable_units": 0,
+ "carrier": None,
+ "client_reference": None,
+ "created_at": "2023-12-14T20:35:55+00:00",
+ "created_by": {
+ "email_address": "grsrbsrgsrf@fake.gov",
+ "id": "de059e0a-42e5-48bb-939e-4f76804ab739",
+ "name": "grsrbsrgsrf",
+ },
+ "document_download_count": None,
+ "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81",
+ "international": False,
+ "job": {
+ "id": "55b242b5-9f62-4271-aff7-039e9c320578",
+ "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv",
+ },
+ "job_row_number": 0,
+ "key_name": None,
+ "key_type": "normal",
+ "normalised_to": "+16615555555",
+ "notification_type": "sms",
+ "personalisation": {
+ "dayofweek": "2",
+ "favecolor": "3",
+ "phonenumber": "+16615555555",
+ },
+ "phone_prefix": "1",
+ "provider_response": None,
+ "rate_multiplier": 1.0,
+ "reference": None,
+ "reply_to_text": "development",
+ "sent_at": None,
+ "sent_by": None,
+ "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1",
+ "status": "created",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+16615555555",
+ "updated_at": None,
+ }
+ ],
+ "page_size": 50,
+ "total": 1,
+}
+
+MOCK_ONE_OFF_JOB = {
+ "data": {
+ "api_key": "mocked_api_key",
+ "billable_units": 1,
+ "carrier": "mocked_carrier",
+ "client_reference": "mocked_client_reference",
+ "created_at": "2024-01-04T20:43:52+00:00",
+ "created_by": {
+ "email_address": "mocked_email@example.com",
+ "id": "mocked_user_id",
+ "name": "mocked_user",
+ },
+ "document_download_count": None,
+ "id": "mocked_notification_id",
+ "international": False,
+ "job": {"id": "mocked_job_id", "original_file_name": "mocked_file.txt"},
+ "job_row_number": 0,
+ "key_name": "mocked_key_name",
+ "key_type": "normal",
+ "normalised_to": "+12133166548",
+ "notification_type": "sms",
+ "personalisation": {"phonenumber": "+12133166548"},
+ "phone_prefix": "1",
+ "provider_response": "mocked_provider_response",
+ "rate_multiplier": 1.0,
+ "reference": "mocked_reference",
+ "reply_to_text": "mocked_reply_text",
+ "sent_at": "2024-01-04T20:43:53+00:00",
+ "sent_by": "mocked_sender",
+ "service": "mocked_service_id",
+ "status": "sending",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+12133166548",
+ "updated_at": "2024-01-04T20:43:53+00:00",
+ }
+}
+
stub_template_stats = [
{
"template_type": "sms",
@@ -116,7 +218,11 @@ def test_get_started(
"app.template_statistics_client.get_template_statistics_for_service",
return_value=copy.deepcopy(stub_template_stats),
)
-
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -142,6 +248,11 @@ def test_get_started_is_hidden_once_templates_exist(
"app.template_statistics_client.get_template_statistics_for_service",
return_value=copy.deepcopy(stub_template_stats),
)
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -164,7 +275,11 @@ def test_inbound_messages_not_visible_to_service_without_permissions(
mock_get_inbound_sms_summary,
):
service_one["permissions"] = []
-
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -188,6 +303,11 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_messages(
mock_get_inbound_sms_summary,
):
service_one["permissions"] = ["inbound_sms"]
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -215,6 +335,11 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_no_messages(
mock_get_inbound_sms_summary_with_no_messages,
):
service_one["permissions"] = ["inbound_sms"]
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -460,7 +585,11 @@ def test_should_show_recent_templates_on_dashboard(
"app.template_statistics_client.get_template_statistics_for_service",
return_value=copy.deepcopy(stub_template_stats),
)
-
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -513,7 +642,11 @@ def test_should_not_show_recent_templates_on_dashboard_if_only_one_template_used
"app.template_statistics_client.get_template_statistics_for_service",
return_value=stats,
)
-
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
main = page.select_one("main").text
@@ -638,6 +771,7 @@ def test_monthly_has_equal_length_tables(
@freeze_time("2016-01-01 11:09:00.061258")
def test_should_show_upcoming_jobs_on_dashboard(
+ mocker,
client_request,
mock_get_service_templates,
mock_get_template_statistics,
@@ -648,6 +782,11 @@ def test_should_show_upcoming_jobs_on_dashboard(
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary,
):
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -685,6 +824,11 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_count_is_0(
"soonest_scheduled_for": None,
},
)
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -706,6 +850,11 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_service_has_no_jobs(
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary,
):
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -755,7 +904,11 @@ def test_correct_font_size_for_big_numbers(
service_one["permissions"] = permissions
mocker.patch("app.main.views.dashboard.get_dashboard_totals", return_value=totals)
-
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=service_one["id"],
@@ -773,6 +926,7 @@ def test_correct_font_size_for_big_numbers(
def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page(
+ mocker,
client_request,
service_one,
mock_get_service_templates,
@@ -784,6 +938,11 @@ def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page(
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary,
):
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -1020,6 +1179,20 @@ def _test_dashboard_menu(client_request, mocker, usr, service, permissions):
return client_request.get("main.service_dashboard", service_id=service["id"])
+def _test_settings_menu(client_request, mocker, usr, service, permissions):
+ usr["permissions"][str(service["id"])] = permissions
+ usr["services"] = [service["id"]]
+ mocker.patch("app.user_api_client.check_verify_code", return_value=(True, ""))
+ mocker.patch(
+ "app.service_api_client.get_services", return_value={"data": [service]}
+ )
+ mocker.patch("app.user_api_client.get_user", return_value=usr)
+ mocker.patch("app.user_api_client.get_user_by_email", return_value=usr)
+ mocker.patch("app.service_api_client.get_service", return_value={"data": service})
+ client_request.login(usr)
+ return client_request.get("main.service_dashboard", service_id=service["id"])
+
+
def test_menu_send_messages(
client_request,
mocker,
@@ -1036,6 +1209,11 @@ def test_menu_send_messages(
):
service_one["permissions"] = ["email", "sms"]
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = _test_dashboard_menu(
client_request,
mocker,
@@ -1051,11 +1229,8 @@ def test_menu_send_messages(
)
in page
)
- # assert url_for('main.uploads', service_id=service_one['id']) in page
- assert url_for("main.manage_users", service_id=service_one["id"]) in page
-
- assert url_for("main.service_settings", service_id=service_one["id"]) not in page
- # assert url_for('main.api_keys', service_id=service_one['id']) not in page
+ assert url_for("main.manage_users", service_id=service_one["id"]) not in page
+ assert url_for("main.service_settings", service_id=service_one["id"]) in page
def test_menu_manage_service(
@@ -1071,6 +1246,11 @@ def test_menu_manage_service(
mock_get_inbound_sms_summary,
mock_get_free_sms_fragment_limit,
):
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = _test_dashboard_menu(
client_request,
mocker,
@@ -1086,10 +1266,44 @@ def test_menu_manage_service(
)
in page
)
- assert url_for("main.manage_users", service_id=service_one["id"]) in page
- assert url_for("main.service_settings", service_id=service_one["id"]) in page
+ assert url_for(".service_dashboard", service_id=service_one["id"]) in page
+ assert url_for(".choose_template", service_id=service_one["id"]) in page
- # assert url_for('main.api_keys', service_id=service_one['id']) not in page
+
+def test_menu_main_settings(
+ client_request,
+ mocker,
+ api_user_active,
+ service_one,
+ mock_get_service_templates,
+ mock_has_no_jobs,
+ mock_get_template_statistics,
+ mock_get_service_statistics,
+ mock_get_annual_usage_for_service,
+ mock_get_inbound_sms_summary,
+ mock_get_free_sms_fragment_limit,
+):
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+ page = _test_settings_menu(
+ client_request,
+ mocker,
+ api_user_active,
+ service_one,
+ ["view_activity", "user_profile", "manage_users", "manage_settings"],
+ )
+ page = str(page)
+ assert (
+ url_for(
+ "main.service_settings",
+ service_id=service_one["id"],
+ )
+ in page
+ )
+ assert url_for("main.service_settings", service_id=service_one["id"]) in page
def test_menu_manage_api_keys(
@@ -1105,6 +1319,11 @@ def test_menu_manage_api_keys(
mock_get_inbound_sms_summary,
mock_get_free_sms_fragment_limit,
):
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = _test_dashboard_menu(
client_request,
mocker,
@@ -1122,8 +1341,8 @@ def test_menu_manage_api_keys(
)
in page
)
- assert url_for("main.manage_users", service_id=service_one["id"]) in page
- assert url_for("main.service_settings", service_id=service_one["id"]) in page
+ # assert url_for("main.manage_users", service_id=service_one["id"]) not in page
+ # assert url_for("main.service_settings", service_id=service_one["id"]) not in page
assert url_for("main.api_integration", service_id=service_one["id"]) in page
@@ -1140,13 +1359,18 @@ def test_menu_all_services_for_platform_admin_user(
mock_get_inbound_sms_summary,
mock_get_free_sms_fragment_limit,
):
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = _test_dashboard_menu(
client_request, mocker, platform_admin_user, service_one, []
)
page = str(page)
assert url_for("main.choose_template", service_id=service_one["id"]) in page
- assert url_for("main.manage_users", service_id=service_one["id"]) in page
- assert url_for("main.service_settings", service_id=service_one["id"]) in page
+ # assert url_for("main.manage_users", service_id=service_one["id"]) in page
+ # assert url_for("main.service_settings", service_id=service_one["id"]) in page
# assert url_for('main.view_notifications', service_id=service_one['id'], message_type='email') in page
assert (
url_for(
@@ -1180,7 +1404,11 @@ def test_route_for_service_permissions(
mocker.patch(
"app.service_api_client.get_global_notification_count", side_effect=_get
)
-
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
validate_route_permission(
mocker,
notify_admin,
@@ -1232,6 +1460,11 @@ def test_service_dashboard_updates_gets_dashboard_totals(
"sms": {"requested": 456, "delivered": 0, "failed": 0},
},
)
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
@@ -1345,6 +1578,7 @@ def test_get_tuples_of_financial_years_defaults_to_2015():
def test_org_breadcrumbs_do_not_show_if_service_has_no_org(
+ mocker,
client_request,
mock_get_template_statistics,
mock_get_service_templates_when_no_templates_exist,
@@ -1352,6 +1586,11 @@ def test_org_breadcrumbs_do_not_show_if_service_has_no_org(
mock_get_annual_usage_for_service,
mock_get_free_sms_fragment_limit,
):
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert not page.select(".navigation-organization-link")
@@ -1414,6 +1653,11 @@ def test_org_breadcrumbs_show_if_user_is_a_member_of_the_services_org(
id_=ORGANISATION_ID,
),
)
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert page.select_one(".navigation-organization-link")["href"] == url_for(
@@ -1445,6 +1689,13 @@ def test_org_breadcrumbs_do_not_show_if_user_is_a_member_of_the_services_org_but
)
mocker.patch("app.models.service.Organization")
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert not page.select(".navigation-breadcrumb")
@@ -1476,6 +1727,13 @@ def test_org_breadcrumbs_show_if_user_is_platform_admin(
),
)
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
client_request.login(platform_admin_user, service_one_json)
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
@@ -1504,6 +1762,13 @@ def test_breadcrumb_shows_if_service_is_suspended(
mocker.patch(
"app.service_api_client.get_service", return_value={"data": service_one_json}
)
+
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert "Suspended" in page.select_one(".navigation-service-name").text
@@ -1532,11 +1797,21 @@ def test_service_dashboard_shows_usage(
"count": 500,
},
)
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
service_one["permissions"] = permissions
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
- table_rows = page.find_all("tbody")[0].find_all("tr")
+ 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
@@ -1565,9 +1840,43 @@ def test_service_dashboard_shows_free_allowance(
}
],
)
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
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
+
+
+def test_service_dashboard_shows_batched_jobs(
+ mocker,
+ client_request,
+ service_one,
+ mock_get_service_templates,
+ mock_get_template_statistics,
+ mock_has_no_jobs,
+ mock_get_annual_usage_for_service,
+ mock_get_free_sms_fragment_limit,
+):
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
+ page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
+
+ job_table = page.find("div", class_="job-table")
+
+ # Check if the "Job" table exists
+ assert job_table is not None
+
+ table_rows = job_table.find_all("tbody")[0].find_all("tr")
+
+ assert len(table_rows) == 1
diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py
index a6877a142..9bdabf925 100644
--- a/tests/app/main/views/test_manage_users.py
+++ b/tests/app/main/views/test_manage_users.py
@@ -27,7 +27,7 @@ from tests.conftest import (
(
create_active_user_with_permissions(),
(
- "Test User (you) "
+ "Test User(you) "
"Permissions "
"Can See dashboard "
"Can Send messages "
@@ -39,18 +39,18 @@ from tests.conftest import (
),
(
create_active_user_empty_permissions(),
- ("Test User With Empty Permissions (you) " "Permissions"),
+ ("Test User With Empty Permissions(you) " "Permissions"),
False,
),
(
create_active_user_view_permissions(),
- ("Test User With Permissions (you) " "Permissions " "Can See dashboard"),
+ ("Test User With Permissions(you) " "Permissions " "Can See dashboard"),
False,
),
(
create_active_user_manage_template_permissions(),
(
- "Test User With Permissions (you) "
+ "Test User With Permissions(you) "
"Permissions "
"Can See dashboard "
"Can Add and edit templates"
@@ -232,7 +232,7 @@ def test_should_show_caseworker_on_overview_page(
assert normalize_spaces(page.select_one("h1").text) == "Team members"
assert normalize_spaces(page.select(".user-list-item")[0].text) == (
- "Test User With Permissions (you) " "Permissions " "Can See dashboard"
+ "Test User With Permissions(you) " "Permissions " "Can See dashboard"
)
# [1:5] are invited users
assert normalize_spaces(page.select(".user-list-item")[6].text) == (
@@ -1240,7 +1240,7 @@ def test_cancel_invited_user_doesnt_work_if_user_not_invited_to_this_service(
(
"pending",
(
- "invited_user@test.gsa.gov (invited) "
+ "invited_user@test.gsa.gov(invited) "
"Permissions "
"Can See dashboard "
"Can Send messages "
@@ -1252,7 +1252,7 @@ def test_cancel_invited_user_doesnt_work_if_user_not_invited_to_this_service(
(
"cancelled",
(
- "invited_user@test.gsa.gov (cancelled invite) "
+ "invited_user@test.gsa.gov(cancelled invite) "
"Permissions"
# all permissions are greyed out
),
@@ -1413,10 +1413,7 @@ def test_manage_user_page_shows_how_many_folders_user_can_view(
user_div = page.select_one(
"h2[title='notify@digital.cabinet-office.gov.uk']"
).parent
- assert (
- user_div.select_one(".tick-cross-list-hint:last-child").text.strip()
- == expected_message
- )
+ assert user_div.select_one(".tick-cross-list-hint").text.strip() == expected_message
def test_manage_user_page_doesnt_show_folder_hint_if_service_has_no_folders(
diff --git a/tests/app/main/views/test_sign_out.py b/tests/app/main/views/test_sign_out.py
index 9ab3ad3cc..899216cd6 100644
--- a/tests/app/main/views/test_sign_out.py
+++ b/tests/app/main/views/test_sign_out.py
@@ -1,5 +1,107 @@
from tests.conftest import SERVICE_ONE_ID
+FAKE_ONE_OFF_NOTIFICATION = {
+ "links": {},
+ "notifications": [
+ {
+ "api_key": None,
+ "billable_units": 0,
+ "carrier": None,
+ "client_reference": None,
+ "created_at": "2023-12-14T20:35:55+00:00",
+ "created_by": {
+ "email_address": "grsrbsrgsrf@fake.gov",
+ "id": "de059e0a-42e5-48bb-939e-4f76804ab739",
+ "name": "grsrbsrgsrf",
+ },
+ "document_download_count": None,
+ "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81",
+ "international": False,
+ "job": {
+ "id": "55b242b5-9f62-4271-aff7-039e9c320578",
+ "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv",
+ },
+ "job_row_number": 0,
+ "key_name": None,
+ "key_type": "normal",
+ "normalised_to": "+16615555555",
+ "notification_type": "sms",
+ "personalisation": {
+ "dayofweek": "2",
+ "favecolor": "3",
+ "phonenumber": "+16615555555",
+ },
+ "phone_prefix": "1",
+ "provider_response": None,
+ "rate_multiplier": 1.0,
+ "reference": None,
+ "reply_to_text": "development",
+ "sent_at": None,
+ "sent_by": None,
+ "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1",
+ "status": "created",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+16615555555",
+ "updated_at": None,
+ }
+ ],
+ "page_size": 50,
+ "total": 1,
+}
+
+MOCK_ONE_OFF_JOB = {
+ "data": {
+ "api_key": "mocked_api_key",
+ "billable_units": 1,
+ "carrier": "mocked_carrier",
+ "client_reference": "mocked_client_reference",
+ "created_at": "2024-01-04T20:43:52+00:00",
+ "created_by": {
+ "email_address": "mocked_email@example.com",
+ "id": "mocked_user_id",
+ "name": "mocked_user",
+ },
+ "document_download_count": None,
+ "id": "mocked_notification_id",
+ "international": False,
+ "job": {"id": "mocked_job_id", "original_file_name": "mocked_file.txt"},
+ "job_row_number": 0,
+ "key_name": "mocked_key_name",
+ "key_type": "normal",
+ "normalised_to": "+12133166548",
+ "notification_type": "sms",
+ "personalisation": {"phonenumber": "+12133166548"},
+ "phone_prefix": "1",
+ "provider_response": "mocked_provider_response",
+ "rate_multiplier": 1.0,
+ "reference": "mocked_reference",
+ "reply_to_text": "mocked_reply_text",
+ "sent_at": "2024-01-04T20:43:53+00:00",
+ "sent_by": "mocked_sender",
+ "service": "mocked_service_id",
+ "status": "sending",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+12133166548",
+ "updated_at": "2024-01-04T20:43:53+00:00",
+ }
+}
+
def test_render_sign_out_redirects_to_sign_in(client_request):
# TODO with the change to using login.gov, we no longer redirect directly to the sign in page.
@@ -18,6 +120,7 @@ def test_render_sign_out_redirects_to_sign_in(client_request):
def test_sign_out_user(
+ mocker,
client_request,
mock_get_service,
api_user_active,
@@ -36,6 +139,13 @@ def test_sign_out_user(
with client_request.session_transaction() as session:
assert session.get("user_id") is not None
# Check we are logged in
+ mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py
index b139eb9be..110bae914 100644
--- a/tests/app/test_navigation.py
+++ b/tests/app/test_navigation.py
@@ -400,9 +400,9 @@ 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/{}/users".format(SERVICE_ONE_ID),
"/services/{}/usage".format(SERVICE_ONE_ID),
- "/services/{}/service-settings".format(SERVICE_ONE_ID),
+ # "/services/{}/users".format(SERVICE_ONE_ID),
+ # "/services/{}/service-settings".format(SERVICE_ONE_ID),
# '/services/{}/api'.format(SERVICE_ONE_ID),
]
@@ -418,7 +418,7 @@ def test_caseworkers_get_caseworking_navigation(
client_request.login(active_caseworking_user)
page = client_request.get("main.choose_template", service_id=SERVICE_ONE_ID)
assert normalize_spaces(page.select_one("header + .grid-container nav").text) == (
- "Send messages Sent messages Team members"
+ "Send messages Sent messages"
)
@@ -433,5 +433,5 @@ def test_caseworkers_see_jobs_nav_if_jobs_exist(
client_request.login(active_caseworking_user)
page = client_request.get("main.choose_template", service_id=SERVICE_ONE_ID)
assert normalize_spaces(page.select_one("header + .grid-container nav").text) == (
- "Send messages Sent messages Team members"
+ "Send messages Sent messages"
)
diff --git a/tests/conftest.py b/tests/conftest.py
index 9bab089f8..ec4ff8ad1 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2323,6 +2323,10 @@ def client_request(logged_in_client, mocker, service_one): # noqa (C901 too com
"app.service_api_client.get_global_notification_count", side_effect=_get
)
+ mocker.patch(
+ "app.billing_api_client.create_or_update_free_sms_fragment_limit", autospec=True
+ )
+
class ClientRequest:
@staticmethod
@contextmanager
@@ -3594,3 +3598,21 @@ def end_to_end_authenticated_context(browser):
context = browser.new_context(storage_state=auth_state_path)
return context
+
+
+@pytest.fixture()
+def fake_markdown_file():
+ input = "#Test"
+ return input
+
+
+@pytest.fixture()
+def fake_jinja_template():
+ input = "{% if True %}True{% endif %}"
+ return input
+
+
+@pytest.fixture()
+def fake_soup_template():
+ input = "Test "
+ return input