Compare commits

...

65 Commits

Author SHA1 Message Date
Carlo Costino
27385bc45a Merge pull request #1170 from GSA/fix-infrastructure-checks
Fix Terraform commands and actions
2024-01-30 15:04:35 -05:00
Carlo Costino
f097b9ca07 Merge pull request #1167 from GSA/notify-admin-1148
notify-admin-1148 make sure one-off files get written to db
2024-01-30 11:38:58 -05:00
Carlo Costino
50e3560c6f Remove the profile attribute entirely
This was what was apparently breaking our Terraform actions.  I traced this to an undocumented breaking change with the AWS provider; more details can be seen here: https://discuss.hashicorp.com/t/error-error-configuring-terraform-aws-provider-failed-to-get-shared-config-profile-default/39417/2

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-01-30 11:06:13 -05:00
Carlo Costino
4da5c14751 Updated 18F/terraform-cloudgov to v0.7.1
Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-01-30 09:40:14 -05:00
Carlo Costino
0f167a91cc Update Cloud Foundry Terraform module
This changeset updates the Cloud Foundry Terraform to its latest release.  Our file were previously referencing a very old version, which was contributing to infrastructure check and deployment failures.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-01-30 09:26:04 -05:00
Kenneth Kehl
7c5674c4e1 notify-admin-1137 make sure one-off files get written to db 2024-01-29 13:17:50 -08:00
Carlo Costino
4e488fa079 Merge pull request #1162 from GSA/1161-bug-fix-ui-time-sent-column-to-match-time-the-message-was-created_at
BUG: Fix UI Time Sent column to match time the message was created_at
2024-01-26 16:48:40 -05:00
Beverly Nguyen
5d480b3f53 updated time 2024-01-26 13:37:49 -08:00
Carlo Costino
a5eb311f83 Merge pull request #1129 from GSA/1114-reviseexpand-on-recent-batch-table-on-the-dashboard-page
Revise/expand on "Recent Batch" table on the dashboard page.
2024-01-26 15:03:08 -05:00
Beverly Nguyen
6b4a5d009c removed comment 2024-01-26 11:51:52 -08:00
Beverly Nguyen
1f12dd3908 show only the first 5 rows 2024-01-26 11:47:46 -08:00
Beverly Nguyen
f7924320df adjusted styling 2024-01-26 11:32:42 -08:00
Beverly Nguyen
1b4754386c update testing for signout and accept invite 2024-01-26 11:29:03 -08:00
Beverly Nguyen
35b38f838c fixed testing for dashboard.py 2024-01-26 11:16:45 -08:00
Beverly Nguyen
45ffa70c3c fixing mocker patch for jobs 2024-01-26 09:43:41 -08:00
Carlo Costino
e48cb94ccb Merge pull request #1143 from GSA/963-message-parts-content-updates
963 message parts content updates
2024-01-26 09:40:17 -05:00
Carlo Costino
0fc14131b1 Merge pull request #1147 from GSA/dependabot/pip/newrelic-9.6.0
Bump newrelic from 9.5.0 to 9.6.0
2024-01-26 09:28:38 -05:00
Beverly Nguyen
6e05fd5147 removed comments 2024-01-25 15:39:32 -08:00
dependabot[bot]
c79c824b24 Bump newrelic from 9.5.0 to 9.6.0
Bumps [newrelic](https://github.com/newrelic/newrelic-python-agent) from 9.5.0 to 9.6.0.
- [Release notes](https://github.com/newrelic/newrelic-python-agent/releases)
- [Commits](https://github.com/newrelic/newrelic-python-agent/compare/v9.5.0...v9.6.0)

---
updated-dependencies:
- dependency-name: newrelic
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-25 23:32:58 +00:00
Beverly Nguyen
524a1e4687 updated download links and table html 2024-01-25 15:32:44 -08:00
Carlo Costino
9513069b42 Merge pull request #1146 from GSA/tdlowden-patch-1
Create manual_qa_template.yml
2024-01-25 16:16:49 -05:00
Tim Lowden
af8309a2ce Update manual_qa_template.yml 2024-01-25 16:07:58 -05:00
Tim Lowden
37086cb03b Update manual_qa_template.yml 2024-01-25 15:57:29 -05:00
Tim Lowden
3ac9d44b2a Update manual_qa_template.yml 2024-01-25 15:56:28 -05:00
Tim Lowden
d5210427f0 Create manual_qa_template.yml
Just starting this
2024-01-25 15:52:59 -05:00
Jonathan Bobel
da1e2a2700 Merge branch 'main' of https://github.com/GSA/notifications-admin into 963-message-parts-content-updates 2024-01-25 14:55:22 -05:00
Jonathan Bobel
d9875ef287 Content updates for Pricing, Trial Mode, and various areas of changing "message parts" 2024-01-25 14:55:17 -05:00
Steven Reilly
cdd262365f Merge pull request #1141 from GSA/update-utils-0-2-7
Update notifications-utils to 0.2.7
2024-01-24 16:05:48 -05:00
Carlo Costino
195c68037b Merge pull request #1140 from GSA/notify-admin-1090
change download reports to 12 hour time (notify-admin-1090)
2024-01-24 15:53:22 -05:00
Carlo Costino
d5daab67cf Fixes tests to match recent notifications-utils updates
Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-01-24 15:35:09 -05:00
Jonathan Bobel
782ec1c5eb 963 - content updates 2024-01-24 15:25:19 -05:00
Kenneth Kehl
fd8981d5cd add timezone to file name 2024-01-24 11:59:31 -08:00
Carlo Costino
1709d3279b Update notifications-utils to 0.2.7
This changeset updates the notifications-utils library to 0.2.7.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-01-24 14:55:55 -05:00
Kenneth Kehl
2126ba8038 fix name of download file 2024-01-24 11:54:25 -08:00
Beverly Nguyen
c9ed1ab59d change time column 2024-01-24 11:15:35 -08:00
Kenneth Kehl
a38518777c change download reports to 12 hour time 2024-01-24 11:03:19 -08:00
Carlo Costino
3379259e14 Merge pull request #1136 from GSA/dependabot/pip/python-dotenv-1.0.1
Bump python-dotenv from 1.0.0 to 1.0.1
2024-01-24 13:55:52 -05:00
dependabot[bot]
ede82d76d6 Bump python-dotenv from 1.0.0 to 1.0.1
Bumps [python-dotenv](https://github.com/theskumar/python-dotenv) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/theskumar/python-dotenv/releases)
- [Changelog](https://github.com/theskumar/python-dotenv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/theskumar/python-dotenv/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: python-dotenv
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-24 18:34:27 +00:00
Carlo Costino
5970dd8055 Merge pull request #1135 from GSA/dependabot/pip/bandit-1.7.7
Bump bandit from 1.7.6 to 1.7.7
2024-01-24 13:31:53 -05:00
Beverly Nguyen
65bccb3f95 changed time column 2024-01-23 16:18:43 -08:00
dependabot[bot]
75a4e1ae6d Bump bandit from 1.7.6 to 1.7.7
Bumps [bandit](https://github.com/PyCQA/bandit) from 1.7.6 to 1.7.7.
- [Release notes](https://github.com/PyCQA/bandit/releases)
- [Commits](https://github.com/PyCQA/bandit/compare/1.7.6...1.7.7)

---
updated-dependencies:
- dependency-name: bandit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-23 23:36:48 +00:00
Beverly Nguyen
8baf9682a1 update table css and format 2024-01-23 15:24:57 -08:00
Steven Reilly
6b00cf1270 Merge pull request #1133 from GSA/set-no-proxy-exclusion
Add no_proxy environment variable
2024-01-22 16:27:45 -05:00
Carlo Costino
490174ab71 Add no_proxy environment variable
This changeset adds the no_proxy environment variable to be set at the time our app runs to exclude the internal apps running within our platform that are handled by Cloud Foundry network policies instead.  Without this in place, the egress proxy will not allow our deployed apps to talk with one another.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-01-22 15:36:00 -05:00
Carlo Costino
f15643c04d Merge pull request #1130 from GSA/adjust-egress-proxy-config
Add missing egress proxy configuration
2024-01-19 17:19:09 -05:00
Carlo Costino
d253b601a2 Add missing egress proxy configuration
This changeset adds some egress proxy configuration that is currently missing from the application initialization.

Signed-off-by: Carlo Costino <carlo.costino@gsa.gov>
2024-01-19 17:14:11 -05:00
Beverly Nguyen
9b0dc3dc3c updated table titles and fields 2024-01-19 11:44:25 -08:00
Carlo Costino
443c7d9992 Merge pull request #1106 from GSA/tdlowden-patch-1
Update sprint-goals.md
2024-01-18 16:27:44 -05:00
Tim Lowden
33433a3822 Update sprint-goals.md 2024-01-18 12:45:03 -05:00
Tim Lowden
c7a393e620 Update sprint-goals.md 2024-01-18 12:43:55 -05:00
Tim Lowden
ba3a229d60 Update sprint-goals.md 2024-01-18 12:42:33 -05:00
Jonathan Bobel
17b1352f01 Merge pull request #1121 from GSA/tdlowden-patch-3
Update bug_template.yml
2024-01-18 11:38:32 -05:00
Tim Lowden
a1440ae36f Update bug_template.yml 2024-01-18 11:33:20 -05:00
Tim Lowden
d49fb8867e Update bug_template.yml 2024-01-18 11:30:43 -05:00
Carlo Costino
98b11dc3c4 Merge pull request #1103 from GSA/818-allow-single-messages-to-be-scheduled
Allow single messages to be scheduled #818
2024-01-18 11:07:00 -05:00
Carlo Costino
6fe29a2bdd Merge pull request #1101 from GSA/1089-change-team-member-card-width
1089 change team member card width
2024-01-18 10:30:00 -05:00
Tim Lowden
b463d420ef Merge pull request #1119 from GSA/fixing-the-bug-template
Replicated the User Story Template in a few ways to see if that fixes…
2024-01-18 10:28:04 -05:00
Carlo Costino
2a9f60c32e Merge pull request #1100 from GSA/1022-sentence-about-5-minutes-til-message-receipts-doesnt-appear
Sentence about 5 minutes til message receipts doesn't appear #1022
2024-01-18 10:19:14 -05:00
Tim Lowden
adda14342b Update sprint-goals.md 2024-01-17 10:07:55 -05:00
Beverly Nguyen
d3c3183573 added choose time form 2024-01-16 15:37:56 -08:00
Beverly Nguyen
25305d0706 update testing 2024-01-16 12:01:39 -08:00
Beverly Nguyen
0e110201d6 update testing 2024-01-16 11:28:58 -08:00
Beverly Nguyen
11a85d688a added notifications status 2024-01-15 14:17:18 -08:00
Jonathan Bobel
82863b02ce Merge branch 'main' of https://github.com/GSA/notifications-admin into 1089-change-team-member-card-width 2024-01-11 16:00:04 -05:00
Jonathan Bobel
27c74ca773 1089- styling update to Team Member cards 2024-01-11 15:56:17 -05:00
39 changed files with 542 additions and 518 deletions

View File

@@ -5,8 +5,6 @@ title: "Bug: [Brief description of the bug]"
labels:
- "Type: Bug"
assignees:
body:
- type: markdown
attributes:
@@ -29,6 +27,17 @@ body:
value: "A bug happened!"
validations:
required: true
- type: dropdown
id: environment
attributes:
label: Environment
description: In which environment did you experience the bug?
options:
- staging
- demo
- production
validations:
required: true
- type: textarea
id: page
attributes:
@@ -36,6 +45,13 @@ body:
description: please include URLs of where you saw the bug
validations:
required: true
- type: textarea
id: reproSteps
attributes:
label: "Detail the steps for someone to reproduce"
description: "Reproduction steps"
validations:
required: true
- type: dropdown
id: browsers
attributes:
@@ -52,4 +68,4 @@ body:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell
required: false

View File

@@ -0,0 +1,82 @@
name: "QA Checklist"
description: "Use this template to manually QA for prod push"
title: "QA for prod push: Admin #[insert PR number], API #[insert PR number], Utils #[insert PR number]"
body:
- type: checkboxes
id: Logging_in
attributes:
label: Log In
options:
- label: notify-staging.app.cloud.gov resolves
- label: Clicking sign in takes you to login page
- label: 2FA code is sent promptly
- label: 2FA code input screen appears as expected
- label: After code is entered, you are brought to the “choose service” screen
- label: Choosing a service brings you to the dashboard for that service
- type: checkboxes
id: send_template
attributes:
label: Create a template and send
options:
- label: Clicking “Send messages” brings you to templates page
- label: When “Create a template” button is clicked, the option to create new or copy existing template appears
- label: Uploading a csv is quick and automatically directs you to the preview/send page
- label: Formatting looks clean and in place
- label: Message dashboard populates and shows “pending” until call is made to AWS for delivery receipt
- type: checkboxes
id: send__copied_template
attributes:
label: Send a message from copied template
options:
- label: When “Create a template” button is clicked, the option to create new or copy existing template appears
- label: Clicking “copy an existing template” allows you to choose from pre-existing template
- label: After editing, the new template saves and you prepare to send
- type: checkboxes
id: send__existing_template
attributes:
label: Send a message from existing template
options:
- label: Clicking on existing template brings you to preview screen with `prepare to send` button
- label: All else above persists as true (formatting, send, dashboard, etc)
- type: checkboxes
id: download_report
attributes:
label: Download the csv delivery report
options:
- label: Clicking on the csv download directly after a message send downloads a report that contains the data we expect
- label: The batch report appears on the main dashboard batch table with accurate information
- label: Clicking on the csv download link from the main dashboard downloads a report that contains the data we expect
- type: checkboxes
id: invite_member
attributes:
label: Invite a team member
options:
- label: Clicking on “Team” in settings brings you to team member screen
- label: Clicking “Invite a team member” brings you to email and permissions screen
- label: Clicking invite team member sends an invite and shows confirmation
- label: Invite email provides live link that works to register new team member
- type: checkboxes
id: delete_member
attributes:
label: Delete a team member
options:
- label: From team member screen, clicking “Change Details” on a specific profile brings you to that persons profile
- label: Clicking on “Remove this team member” prompts a confirmation
- label: Clicking the confirmation removes the team member
- type: checkboxes
id: sign_out
attributes:
label: Sign Out
options:
- label: Hitting “Sign Out” takes you back to the homepage and logs you out

View File

@@ -4,3 +4,6 @@
##
export NEW_RELIC_PROXY_HOST=$egress_proxy
export http_proxy=$egress_proxy
export https_proxy=$egress_proxy
export no_proxy="apps.internal"

View File

@@ -54,6 +54,7 @@ from app.formatters import (
format_datetime_normal,
format_datetime_relative,
format_datetime_short,
format_datetime_short_america,
format_day_of_week,
format_delta,
format_delta_days,
@@ -548,6 +549,7 @@ def add_template_filters(application):
format_datetime_24h,
format_datetime_normal,
format_datetime_short,
format_datetime_short_america,
valid_phone_number,
linkable_name,
format_date,

View File

@@ -124,6 +124,7 @@ td.table-empty-message {
.button-flex-header {
flex-direction: column;
align-items: flex-start;
margin-bottom: units(2);
@include at-media(desktop) {
flex-direction: row;
display: flex;
@@ -141,10 +142,11 @@ td.table-empty-message {
.user-list {
display: inline-flex;
flex-wrap: wrap;
gap: units(2);
gap: units(4);
.user-list-item {
@include at-media(desktop) {
flex: 1 1 calc(50% - units(2));
flex-grow: 0;
}
flex: 1 1 calc(100% - units(2));
border: 1px solid color('gray-cool-10');
@@ -375,6 +377,32 @@ td.table-empty-message {
}
}
.job-table {
.usa-table {
thead th.file-name {
width: 25%;
}
thead th.template {
width: 20%;
}
thead th.time-sent {
width: 30%;
}
thead th.sender {
width: 15%;
}
thead th.\#-of-recipients {
width: 5%;
}
thead th.report {
width: 5%;
}
th {
padding: 0.5rem 0.5rem;
}
}
}
#template-list {
max-height: 500px;
overflow-y: auto;

View File

@@ -98,6 +98,28 @@ def format_datetime_short(date):
)
def format_datetime_short_america(date):
return "{} at {}".format(format_date_numeric_america(date), format_time_12h(date))
def format_date_numeric_america(date):
date = parse_naive_dt(date)
preferred_tz = pytz.timezone(get_user_preferred_timezone())
return (
date.replace(tzinfo=timezone.utc).astimezone(preferred_tz).strftime("%m-%d-%Y")
)
def format_time_12h(date):
date = parse_naive_dt(date)
preferred_tz = pytz.timezone(get_user_preferred_timezone())
return (
date.replace(tzinfo=timezone.utc).astimezone(preferred_tz).strftime("%I:%M %p")
)
def format_datetime_relative(date):
return "{} at {} {}".format(
get_human_day(date), format_time_24h(date), get_user_preferred_timezone()
@@ -448,6 +470,12 @@ def message_count_noun(count, template_type):
else:
return "text messages"
if template_type == "parts":
if count == 1:
return "text message part"
else:
return "text message parts"
elif template_type == "email":
if count == 1:
return "email"

View File

@@ -47,54 +47,31 @@ 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)
job_response = job_api_client.get_jobs(service_id)
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"],
jobs = [
{
"job_id": job["id"],
"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,
}
)
"notification_count": job["notification_count"],
"created_by": job["created_by"],
}
for job in job_response["data"]
]
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,
jobs=jobs,
service_data_retention_days=service_data_retention_days,
)

View File

@@ -11,12 +11,7 @@ from flask import (
url_for,
)
from app import (
current_service,
format_date_numeric,
job_api_client,
notification_api_client,
)
from app import current_service, job_api_client, notification_api_client
from app.main import main
from app.notify_client.api_key_api_client import KEY_TYPE_TEST
from app.utils import (
@@ -26,7 +21,7 @@ from app.utils import (
parse_filter_args,
set_status_filters,
)
from app.utils.csv import generate_notifications_csv
from app.utils.csv import generate_notifications_csv, get_user_preferred_timezone
from app.utils.templates import get_template
from app.utils.user import user_has_permissions
@@ -146,6 +141,9 @@ def download_notifications_csv(service_id):
service_data_retention_days = current_service.get_days_of_retention(
filter_args.get("message_type")[0]
)
file_time = datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
file_time = f"{file_time} {get_user_preferred_timezone()}"
return Response(
stream_with_context(
generate_notifications_csv(
@@ -162,7 +160,7 @@ def download_notifications_csv(service_id):
mimetype="text/csv",
headers={
"Content-Disposition": 'inline; filename="{} - {} - {} report.csv"'.format(
format_date_numeric(datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")),
file_time,
filter_args["message_type"][0],
current_service.name,
)

View File

@@ -780,6 +780,8 @@ def _check_notification(service_id, template_id, exception=None):
back_link = get_back_link(service_id, template, len(placeholders), placeholders)
choose_time_form = ChooseTimeForm()
if (not session.get("recipient")) or not all_placeholders_in_session(
template.placeholders
):
@@ -791,6 +793,7 @@ def _check_notification(service_id, template_id, exception=None):
return dict(
template=template,
back_link=back_link,
choose_time_form=choose_time_form,
**(get_template_error_dict(exception) if exception else {}),
)
@@ -861,7 +864,7 @@ def send_notification(service_id, template_id):
job_api_client.create_job(
upload_id,
service_id,
scheduled_for="",
scheduled_for=request.form.get("scheduled_for", ""),
template_id=template_id,
original_file_name=filename,
notification_count=1,
@@ -877,14 +880,20 @@ def send_notification(service_id, template_id):
service_id, job_id=upload_id, include_one_off=True
)
attempts = 0
while notifications["total"] == 0 and attempts < 5:
# The response can come back in different forms of incompleteness
while (
notifications["total"] == 0
and notifications["notifications"] == []
and attempts < 50
):
notifications = notification_api_client.get_notifications_for_service(
service_id, job_id=upload_id, include_one_off=True
)
time.sleep(0.1)
attempts = attempts + 1
if notifications["total"] == 0 and attempts == 5:
if notifications["total"] == 0 and attempts == 50:
# This shows the job we auto-generated for the user
return redirect(
url_for(

View File

@@ -29,51 +29,69 @@
{{ ajax_block(partials, updates_url, 'template-statistics') }}
<h2 class="margin-top-4 margin-bottom-1">Batched Jobs</h2>
<h2 class="margin-top-4 margin-bottom-1">Recent Batches</h2>
<div class='job-table'>
{% call(item, row_number) list_table(
notifications,
caption="Batched Jobs",
caption_visible=False,
border_visible=True,
empty_message='No batched job messages found &thinsp;(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() %}
<a class="usa-link file-list-filename" href="/services/{{ item.service }}/jobs/{{ item.job.id }}">{{ item.job.original_file_name|replace('.csv', '') if item.job.id else '' }}</a>
{% 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" %}
<a class="usa-link file-list-filename" href="{{ availability.download_link[0] }}">{{ "Download" if item.job.original_file_name else '' }}</a>
<span>- {{ availability.time_left }}</span>
{% elif availability %}
<span>{{ availability.time_left }}</span>
{% endif %}
{% endcall %}
{% endif %}
{% endcall %}
<table class="usa-table usa-table--borderless width-full">
<thead class="table-field-headings">
<tr>
<th scope="col" class="table-field-heading-first file-name">
<span>File name</span>
</th>
<th scope="col" class="table-field-heading template">
<span>Template</span>
</th>
<th scope="col" class="table-field-heading time-sent">
<span>Time sent</span>
</th>
<th scope="col" class="table-field-heading sender">
<span>Sender</span>
</th>
<th scope="col" class="table-field-heading #-of-recipients">
<span># of Recipients</span>
</th>
<th scope="col" class="table-field-heading report">
<span>Report</span>
</th>
</tr>
</thead>
<tbody>
{% for notification in notifications[:5] %}
{% if notification %}
<tr class="table-row" id="{{ notification.job.id }}">
<th class="table-field">
{{ notification.job.original_file_name if notification.job.original_file_name else 'Manually entered number'}}
<br>
<a class="usa-link file-list-filename" href="/services/{{ notification.service }}/jobs/{{ notification.job.id }}">View Batch</a>
</th>
<th class="table-field">
{{ notification.template.name }}
</th>
<th class="table-field">
{{ notification.created_at | format_datetime_short_america }}
</th>
<th class="table-field">
{{ notification.created_by.name }}
</th>
{% set job_available = jobs|selectattr('job_id', 'equalto', notification.job.id)|first %}
<th class="table-field">
{{ job_available.notification_count if job_available else ''}}
</th>
<th class="table-field">
{% if job_available and job_available.time_left != "Data no longer available" %}
<a class="usa-link file-list-filename" href="{{ job_available.download_link }}">
{{ "Download" if job_available.job_id else '' }}</a>
<span class="usa-hint">{{ job_available.time_left }}</span>
{% elif job_available %}
<span>{{ job_available.time_left }}</span>
{% endif %}
</th>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% if show_pagination %}
{{ previous_next_navigation(prev_page, next_page) }}
{% elif next_page %}
<p class="table-show-more-link">
Only showing the first 50 messages
</p>
{% endif %}
<h2 class="margin-top-4 margin-bottom-1">Usage</h2>
<h3 class="margin-bottom-0">Daily</h3>
<p class="margin-0">Across all services</p>

View File

@@ -38,6 +38,9 @@
</div>
{% endif %}
<p class="notification-status">
Messages will remain in pending state until carrier status is received, typically 5 minutes.
</p>
{% if not job.processing_finished %}
<div
data-module="update-content"

View File

@@ -54,6 +54,16 @@
)}}" class='page-footer'>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
{% if not error %}
{% if choose_time_form %}
{{ choose_time_form.scheduled_for(param_extensions={
'formGroup': {'classes': 'bottom-gutter-2-3'},
'attributes': {
'data-module': 'radio-select',
'data-categories': choose_time_form.scheduled_for.categories|join(','),
'data-show-now-as-default': 'true'
}
}) }}
{% endif %}
{% set button_text %}Send 1 {{ 1|message_count_label(template.template_type, suffix='') }}{% endset %}
{{ usaButton({ "text": button_text }) }}
{% endif %}

View File

@@ -15,62 +15,20 @@ Pricing
{{ content_metadata(
data={
"Last updated": "Feb 27, 2023"
"Last updated": "January 25, 2024"
}
) }}
<p>During pilot period, each service has an allowance of 250,000 free text messages. Once this allowance is met, the
application will stop delivering messages. There's no monthly charge, no setup fee and no procurement cost.</p>
<p>When a new service is added, it will start in <a class="usa-link" href="{{ url_for('main.trial_mode_new') }}">trial mode</a>. Moving a service out of trial mode is subject to approval by
the Notify.gov team.</p>
<p>You'll use more messages towards the allowance if you send text messages longer than 160 characters.
</p>
<p>During the pilot period, each service has an allowance of 250,000 message parts. Once this allowance is met, the
application will stop delivering messages. There's no monthly charge, no setup fee, and no procurement cost. You'll use
more parts towards the allowance if you:</p>
<p>
{% if not current_user.is_authenticated %}
<a class="usa-link" href="{{ url_for('main.register') }}">Create an account,</a> then
add your unique service(s).
</p>
{% else %}
Additional unique services may be added, although moving each service out of trial mode is subject to approval by the
Notify.gov team.
{% endif %}
</p>
<p>When you add a new service it will start in <a class="usa-link"
href="{{ url_for('main.trial_mode_new') }}">trial mode</a>.</p>
<p>Youll use more messages towards the allowance if you send text messages longer than 160 characters.</p>
<!-- <h2 class="font-body-lg" id="emails">Emails</h2>
<p>Its free to send emails through Notify.</p> -->
<h2 class="font-body-lg" id="text-messages">Text messages</h2>
<p>During pilot phase, each service has an annual allowance of 250,000 free text messages. Once this allowance is met,
the application will stop delivering messages.</p>
<!-- <p>When a service has used its annual allowance, it costs {{ sms_rate }} pence (plus VAT) for each text message you send.</p> -->
<p>Youll use more messages towards the allowance, if you send <a class="usa-link"
href="#long-text-messages">text messages longer than 160 characters</a>.</p>
<p>You may also use more messages if you:</p>
<ul class="list list-bullet">
<li>send text messages longer than 160 characters</a></li>
<li>use certain <a class="usa-link" href="#symbols">signs and symbols</a></li>
<li>use <a class="usa-link" href="#accents">accents and accented letters</a></li>
<li>send text messages to <a class="usa-link"
href="#international-numbers">international numbers</a></li>
</ul>
<!-- <p>See <a class="usa-link" href="{{ url_for('main.how_to_pay') }}">how to pay</a>.
<h3 class="font-body-lg" id="free-text-message-allowance">Free text message allowance</h3>
<p>The current allowance is:</p>
<ul class="list list-bullet">
<li>40,000 free text messages for national services</li>
<li>20,000 free text messages for regional services</li>
<li>10,000 free text messages for state-funded schools and GP practices</li>
</ul>
<p>Each unique service you add has a separate allowance.</p> -->
<h3 class="font-body-lg" id="long-text-messages">Long text messages</h3>
<p>If a text message is longer than 160 characters (including spaces), it counts as more than one message.</p>
@@ -82,12 +40,12 @@ Notify.gov team.
caption_visible=False
) %}
{% for message_length, charge in [
('Up to 160 characters', '1 text message'),
('Up to 306 characters', '2 text messages'),
('Up to 459 characters', '3 text messages'),
('Up to 612 characters', '4 text messages'),
('Up to 765 characters', '5 text messages'),
('Up to 918 characters', '6 text messages'),
('Up to 160 characters', '1 part'),
('Up to 306 characters', '2 parts'),
('Up to 459 characters', '3 parts'),
('Up to 612 characters', '4 parts'),
('Up to 765 characters', '5 parts'),
('Up to 918 characters', '6 parts'),
] %}
{% call row() %}
{{ text_field(message_length) }}
@@ -186,11 +144,11 @@ Notify.gov team.
caption_visible=False
) %}
{% for message_length, charge in [
('Up to 70 characters', '1 text message'),
('Up to 134 characters', '2 text messages'),
('Up to 201 characters', '3 text messages'),
('Up to 268 characters', '4 text messages'),
('Each additional 67 characters', '1 additional text message'),
('Up to 70 characters', '1 part'),
('Up to 134 characters', '2 parts'),
('Up to 201 characters', '3 parts'),
('Up to 268 characters', '4 parts'),
('Each additional 67 characters', '1 additional part'),
] %}
{% call row() %}
{{ text_field(message_length) }}
@@ -200,43 +158,9 @@ Notify.gov team.
{% endcall %}
</div>
<h3 class="font-body-lg" id="international-numbers">Sending text messages to international numbers</h3>
<p>It might cost more to send text messages to international numbers than U.S. ones, depending on the country.</p>
<!-- {% set smsIntRates %}
{{ live_search(target_selector='#international-pricing .table-row', show=True, form=search_form, label='Search by country name or code') }}
<h3 class="font-body-lg" id="international-numbers">Trial mode</h3>
<p>When a new service is added, it will start in <a href="/using-notify/trial-mode">trial mode</a>. Moving a service out of trial mode is subject to approval by
the Notify.gov team. Additional unique services may be added, although moving each service out of trial mode is subject
to approval by the Notify.gov team.</p>
<div id="international-pricing" class="bottom-gutter-3-2">
{% call mapping_table(
caption='International text message rates',
field_headings=['Country code', 'Country', 'Cost multiplier'],
field_headings_visible=True,
caption_visible=False
) %}
{% for cc, names, billable_units in international_sms_rates %}
{% call row() %}
{{ text_field('+' + cc) }}
{% call field() %}
{% for name in names %}
{{ name }}<br>
{% endfor %}
{% endcall %}
{{ text_field('{}&hairsp;&times;'.format(billable_units)|safe) }}
{% endcall %}
{% endfor %}
{% call row() %}
{{ text_field('+882') }}
{% call field() %}Worldwide{% endcall %}
{{ text_field('Not supported') }}
{% endcall %}
{% endcall %}
</div>
{% endset %}
{{ usaDetails({
"summaryText": "International text message rates",
"id": "sms-pricing-details",
"html": smsIntRates
}) }} -->
{% endblock %}
{% endblock %}

View File

@@ -7,37 +7,33 @@
{% block content_column_content %}
<h1 class="font-body-2xl margin-bottom-3">Trial mode</h1>
<p>When you add a new service it will start in trial mode. This lets you try out Notify.gov, with a few restrictions.</p>
<p>While your service is in trial mode you can only:</p>
<ul class="list list-bullet">
<li>send 50 text messages per day</li>
<li>send messages to yourself and other people in your team</li>
</ul>
{% if current_service and current_service.trial_mode %}
<p>
To remove these restrictions, you can <a class="usa-link" href="{{ url_for('.support') }}">request to go live</a>.</p>
{% else %}
<p>
To remove these restrictions:
</p>
<ol class="list list-number">
<li><a class="usa-link" href="{{ url_for('.sign_in') }}">Sign in to Notify</a>.</li>
<li>Go to the <b class="bold">Settings</b> page.</li>
<li>Select <b class="bold">Request to go live</b>.</li>
</ol>
{% endif %}
<p>
When we receive your request well get back to you within one working day.
</p>
<h2 class="font-body-lg" id="before-you-request-to-go-live">Before you request to go live</h2>
<p>You must:</p>
<h2 class="font-body-lg">Limits while in trial mode</h2>
<p>While your service is in trial mode you can only:</p>
<ul class="list list-bullet">
<li>add examples of the messages you want to send</li>
<li>update your settings so youre ready to send and receive messages</li>
<li>accept our terms of use</li>
<li>send 50 text message parts per day</li>
<li>send messages to yourself and other people in your team</li>
</ul>
<p>Each text message is made up of one or more parts.</p>
<ul class="list list-bullet">
<li>Generally, 160 characters is one part (and one text message).</li>
<li>A text message of 160-306 characters is two parts.</li>
</ul>
<p>For more information on how message parts are calculated, see
<a href="/using-notify/pricing">Pricing</a>.</p>
<h2 class="font-body-lg">Before going Live</h2>
<p>Before you request to make your service live so you can send messages to clients:</p>
<ul class="list list-bullet">
<li>Add examples of the messages you want to send into Notify.</li>
<li>Update your settings so youre ready to send and receive messages.</li>
<li>Review your Terms of Use in the Memorandum of Understanding.</li>
</ul>
<h3 class="font-body-lg">Going Live</h3>
<p>To remove the restrictions of Trial Mode and begin sending messages to clients complete the <a href="https://airtable.com/appe4n7jYOALPLcyU/shrIPWnLTw9U1fclL">Live Campaign Form</a>.</p>
<p>We'll get back to you within one working day. </p>
{% endblock %}

View File

@@ -19,25 +19,25 @@
</div>
<div id='pill-selected-item'>
<div class='grid-row'>
<div class='grid-col-12'>
<h2 class="heading-small margin-bottom-1">Daily messages across all services</h2>
<p class="margin-0">You have sent {{ global_message_limit - daily_global_messages_remaining }} of your {{ global_message_limit }} daily messages allowance.</p>
<p class="margin-0"></p>You have {{ daily_global_messages_remaining }} messages remaining.</p>
<h2 class='heading-small margin-bottom-1'>Text messages</h2>
<h2 class='heading-small margin-bottom-1'>Text message parts</h2>
<div class="keyline-block">
You have sent
{{ big_number(sms_sent, 'messages of your', smaller=True) }}
{{ big_number(sms_free_allowance, 'free messages allowance.', smaller=True) }}
{{ big_number(sms_sent, 'text message parts of your', smaller=True) }}
{{ big_number(sms_free_allowance, 'free message parts allowance.', smaller=True) }}
<br />
You have
{% if sms_free_allowance > 0 %}
{{ big_number(sms_allowance_remaining, 'messages remaining.', smaller=True) }}
{{ big_number(sms_allowance_remaining, 'message parts remaining.', smaller=True) }}
{% endif %}
{# {% for row in sms_breakdown %}
{% if row.charged_units > 0 %}
{{ big_number(
row.charged_units,
row.charged_units,
'at {:.2f} pence per message'.format(row.rate * 100),
smaller=True
) }}
@@ -90,7 +90,7 @@
{{ item.month }}
{% endcall %}
{% for counts, template_type in [
(item.sms_counts.0, 'sms'),
(item.sms_counts.0, 'parts'),
] %}
{% call field(align='left') %}
{{ big_number(
@@ -126,7 +126,7 @@
{% endif %}
</ul>
{% endcall %}
{% endcall %}
</div>
{% endif %}
@@ -140,7 +140,7 @@
</div>
<div class="grid-col-8">
<p class="align-with-heading-copy">
What counts as 1 text message?<br />
What counts as 1 text message part?<br />
See <a class="usa-link" href="{{ url_for('.pricing') }}">pricing</a>.
</p>
</div>

View File

@@ -170,7 +170,7 @@ def convert_report_date_to_preferred_timezone(db_date_str_in_utc):
utc_date_obj = utc_date_obj.astimezone(pytz.utc)
preferred_timezone = pytz.timezone(get_user_preferred_timezone())
preferred_date_obj = utc_date_obj.astimezone(preferred_timezone)
preferred_tz_created_at = preferred_date_obj.strftime("%Y-%m-%d %H:%M:%S")
preferred_tz_created_at = preferred_date_obj.strftime("%Y-%m-%d %I:%M:%S %p")
return f"{preferred_tz_created_at} {get_user_preferred_timezone()}"

View File

@@ -1,6 +1,17 @@
# Notify Sprint Goals Log
## Sprint: A (1/3/24)
## Sprint: B (1/18/24)
2024 Sprints will be named after mythical creatures
| | Goals | Impact | Milestone(s) |
|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| Engineering | Get `staging` stable and bugs fixed in order to do a prod push, finalize Login.gov implementation, complete epic to [remove phone numbers from db](https://github.com/GSA/notifications-api/issues/667), [calculate pre-send message parts](https://github.com/GSA/notifications-admin/issues/1027) to add to confirmation page, finish [E2E testing implementation](https://github.com/GSA/notifications-admin/issues/677), set up [opt-out lists by phone number](https://github.com/GSA/notifications-api/issues/312) | Install required auth, expand message send volume for partners, allow users to see how many parts a send will cost, improve app stability, partition opt-outs so that they aren't across the system | Building Notify.gov |
| UX | [Tackle `Usage and Performance` epic](https://github.com/GSA/notifications-admin/issues/1108) as highest priority group of user stories, break out UX work into more user-stories and correlating epics, prioritize epic/story order, improve [pre-send confirmation page](https://github.com/GSA/notifications-admin/issues/815)| 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 | Get [refreshed content for `Using Notify` pages](https://github.com/GSA/notifications-admin/issues/449) in place, 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, bring existing content into current state| Building Notify.gov |
| Ops | Assist in Tech to Gov process, plan gap coverage for Tim's departure 2/9 |
## Sprint: Akhlut (1/3/24)
2024 Sprints will be named after mythical creatures
| | Goals | Impact | Milestone(s) |

125
poetry.lock generated
View File

@@ -41,24 +41,24 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
[[package]]
name = "bandit"
version = "1.7.6"
version = "1.7.7"
description = "Security oriented static analyser for python code."
optional = false
python-versions = ">=3.8"
files = [
{file = "bandit-1.7.6-py3-none-any.whl", hash = "sha256:36da17c67fc87579a5d20c323c8d0b1643a890a2b93f00b3d1229966624694ff"},
{file = "bandit-1.7.6.tar.gz", hash = "sha256:72ce7bc9741374d96fb2f1c9a8960829885f1243ffde743de70a19cee353e8f3"},
{file = "bandit-1.7.7-py3-none-any.whl", hash = "sha256:17e60786a7ea3c9ec84569fd5aee09936d116cb0cb43151023258340dbffb7ed"},
{file = "bandit-1.7.7.tar.gz", hash = "sha256:527906bec6088cb499aae31bc962864b4e77569e9d529ee51df3a93b4b8ab28a"},
]
[package.dependencies]
colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
GitPython = ">=3.1.30"
PyYAML = ">=5.3.1"
rich = "*"
stevedore = ">=1.20.0"
[package.extras]
test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"]
baseline = ["GitPython (>=3.1.30)"]
test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"]
toml = ["tomli (>=1.1.0)"]
yaml = ["PyYAML"]
@@ -915,37 +915,6 @@ files = [
{file = "geojson-3.1.0.tar.gz", hash = "sha256:58a7fa40727ea058efc28b0e9ff0099eadf6d0965e04690830208d3ef571adac"},
]
[[package]]
name = "gitdb"
version = "4.0.11"
description = "Git Object Database"
optional = false
python-versions = ">=3.7"
files = [
{file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"},
{file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"},
]
[package.dependencies]
smmap = ">=3.0.1,<6"
[[package]]
name = "gitpython"
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.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 (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"]
[[package]]
name = "govuk-bank-holidays"
version = "0.13"
@@ -1661,40 +1630,40 @@ files = [
[[package]]
name = "newrelic"
version = "9.5.0"
version = "9.6.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.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"},
{file = "newrelic-9.6.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:09dad0db993402e166e37d99302c2ad5588b4ff1e5b814819540ca5ec2bd3cea"},
{file = "newrelic-9.6.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:aeadc0bd8140738af4d7d7ea9d0a3dfa7b6f37e02fe48c0a035ccb6cac881770"},
{file = "newrelic-9.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:16fb3bfde6dca0edab358c2b083386e2e764c463d85ffeb41cd6ce7a279bd7f6"},
{file = "newrelic-9.6.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1ba2fc6ce0b6b9a399a1e5db7e480ba51b431cb3cc6ba136a4c0e76877cf972d"},
{file = "newrelic-9.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fe927f79db2dd5432a362aedde98543e93599b26d5c2e065bc563991be7f347"},
{file = "newrelic-9.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fefffff32e6605ddf26d51030ea1a41b9f7f606fccbb5f8d4b43c36d755acd1"},
{file = "newrelic-9.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0a68dd85a7cf4c5039ea94c8975f86a7c516dd492d8bf08976ab0a459918da9b"},
{file = "newrelic-9.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:74a9a49ef596aa9f7560f6bb1480480b8f9b652c7e03de173ef06b0dc2355799"},
{file = "newrelic-9.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26255aec6b29bc17e5e089bb18fbf004f4c765386ba61d1289681aa7ba538207"},
{file = "newrelic-9.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6f21f7a69294f64daa9be59c1c1e0eba889e85ed83afc67ed0a63c8efc9ff2b"},
{file = "newrelic-9.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5ecd44ff5b39138469778b5224a1a373867d21a0ca2389d093d99939cb422947"},
{file = "newrelic-9.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c6c4aa8b91dcb5403862c3e5efa57751a8b4e778576916bb22cafbfc03d47807"},
{file = "newrelic-9.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f02fb9166533f22fdb465f7a8d4b364178d94561b159cc9922f4d27491fd7d1"},
{file = "newrelic-9.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703ee92dda983eb4dcbcc300750a815ff3a5401995c3cdd587ac785b6d747e0e"},
{file = "newrelic-9.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ce4646554246c6577f5aea358516ad5cf2c35fcef0cd16dbd01bbe89b19b4ccb"},
{file = "newrelic-9.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5fae1f3addb422629c4d3e9d011de40629b4d078073ad0517ad42d4ddc7e8441"},
{file = "newrelic-9.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333bd5aedb4f814447ac3aa1a0cc76910a64f13385aa6481a97f87585af52cbf"},
{file = "newrelic-9.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a5aa22ce7813294ca5dc89a0d86ce4774bb9201028305975efdcf93ef36e0e"},
{file = "newrelic-9.6.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:01c0eb630bb18261241a37aa0a70cb6f706079a1f58f59f2bb64f26fda54ffc5"},
{file = "newrelic-9.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:eb82f31754dadd27676fe6f681b25db7af75eb854e332b92f1ea17584efc322c"},
{file = "newrelic-9.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38256e67b2d295229240c29e9949cdae40c9ac987d51f569f57f52ed2aef027f"},
{file = "newrelic-9.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f681c005b1d44ec01b3fe1a7b4ec589572f681f27130ea367155ac0245e133d4"},
{file = "newrelic-9.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:23d54298bc0a936a17e25fdca52c2eefbc50966985738e31d62cfe12ea3f60ba"},
{file = "newrelic-9.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f346aeb418c9da6cff4382cb4f50d314ae5126a6a6037f01224b0072eb30ff49"},
{file = "newrelic-9.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26560dba7cd36c5c787e3dfd6c3c5d1e8e8605c82dc7ffee143880456d8650e5"},
{file = "newrelic-9.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d168349041922f206b72fa30a930c0752d8b76b4a2fd7774d4cb2843aa804e32"},
{file = "newrelic-9.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:72b378b4aa4a52d2ac14be962f0de747f573b9912365981c0b3fc16bd12b5133"},
{file = "newrelic-9.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ab342ab7e3189e244e44238aa8563eeff3a5b5896b77d2d4dbf2c5d0bfe7d73"},
{file = "newrelic-9.6.0.tar.gz", hash = "sha256:3db642583cfee951f185696fe365b41d8aff733b23867e88de97de114e3cfcf8"},
]
[package.extras]
@@ -1731,7 +1700,7 @@ requests = ">=2.0.0"
[[package]]
name = "notifications-utils"
version = "0.2.6"
version = "0.2.7"
description = ""
optional = false
python-versions = ">=3.9,<3.12"
@@ -1783,7 +1752,7 @@ werkzeug = "^3.0.1"
type = "git"
url = "https://github.com/GSA/notifications-utils.git"
reference = "HEAD"
resolved_reference = "f183d120a8b86e655405694adde1f0d95e4e5a51"
resolved_reference = "b6cee72f45dbcd48b59447fa08bbac59e15a7b98"
[[package]]
name = "numpy"
@@ -2431,13 +2400,13 @@ six = ">=1.5"
[[package]]
name = "python-dotenv"
version = "1.0.0"
version = "1.0.1"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false
python-versions = ">=3.8"
files = [
{file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
{file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
{file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
]
[package.extras]
@@ -2507,6 +2476,7 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@@ -2876,17 +2846,6 @@ files = [
{file = "smartypants-2.0.1-py2.py3-none-any.whl", hash = "sha256:8db97f7cbdf08d15b158a86037cd9e116b4cf37703d24e0419a0d64ca5808f0d"},
]
[[package]]
name = "smmap"
version = "5.0.1"
description = "A pure Python implementation of a sliding window memory map manager"
optional = false
python-versions = ">=3.7"
files = [
{file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"},
{file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"},
]
[[package]]
name = "sortedcontainers"
version = "2.4.0"
@@ -3129,4 +3088,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.12"
content-hash = "4794f0a44c97a4e7b2b49341f56b826d10ac6652dfdbc29323f7e433e10dc65d"
content-hash = "94e6fe2e143f12eaa31f01b5a9206012a6a48908d98daa9fa2f531e986e4a6d3"

View File

@@ -32,7 +32,7 @@ pyexcel-xls = "==0.7.0"
pyexcel-xlsx = "==0.6.0"
openpyxl = "==3.0.10"
pyproj = "==3.6.1"
python-dotenv = "==1.0.0"
python-dotenv = "==1.0.1"
pytz = "==2023.3.post1"
rtreelib = "==0.2.0"
werkzeug = "^3.0.1"

View File

@@ -88,6 +88,8 @@ The below steps rely on you first configuring access to the Terraform state in s
terraform plan
```
If the `terraform init` command fails, you may need to run `terraform init -upgrade` to make sure new module versions are picked up.
1. Apply changes with `terraform apply`.
1. Remove the space deployer service instance if it doesn't need to be used again, such as when manually running terraform once.

View File

@@ -3,7 +3,7 @@ terraform {
required_providers {
cloudfoundry = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.15.5"
version = "0.53.0"
}
}
}

View File

@@ -7,7 +7,7 @@ locals {
}
module "redis" {
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.2.0"
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
@@ -17,7 +17,7 @@ module "redis" {
}
module "logo_upload_bucket" {
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.2.0"
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name

View File

@@ -3,7 +3,7 @@ terraform {
required_providers {
cloudfoundry = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.15.5"
version = "0.53.0"
}
}
@@ -12,7 +12,6 @@ terraform {
key = "admin.tfstate.demo"
encrypt = "true"
region = "us-gov-west-1"
profile = "notify-terraform-backend"
}
}

View File

@@ -11,7 +11,7 @@ data "cloudfoundry_space" "dev" {
}
module "logo_upload_bucket" {
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.2.0"
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name

View File

@@ -3,7 +3,7 @@ terraform {
required_providers {
cloudfoundry = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.50.7"
version = "0.53.0"
}
}
}

View File

@@ -47,7 +47,7 @@ if [[ ! -s "secrets.auto.tfvars" ]]; then
fi
echo "Importing terraform state for $username"
terraform init
terraform init -upgrade
key_name=$username-admin-dev-key

View File

@@ -7,7 +7,7 @@ locals {
}
module "redis" {
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.2.0"
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
@@ -17,7 +17,7 @@ module "redis" {
}
module "logo_upload_bucket" {
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.2.0"
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
@@ -50,7 +50,7 @@ module "api_network_route" {
# https://cloud.gov/docs/services/external-domain-service/#how-to-create-an-instance-of-this-service
###########################################################################
module "domain" {
source = "github.com/18f/terraform-cloudgov//domain?ref=v0.5.2"
source = "github.com/18f/terraform-cloudgov//domain?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name

View File

@@ -3,7 +3,7 @@ terraform {
required_providers {
cloudfoundry = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.15.5"
version = "0.53.0"
}
}
@@ -12,7 +12,6 @@ terraform {
key = "admin.tfstate.prod"
encrypt = "true"
region = "us-gov-west-1"
profile = "notify-terraform-backend"
}
}

View File

@@ -7,7 +7,7 @@ locals {
}
module "redis" {
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.2.0"
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
@@ -17,7 +17,7 @@ module "redis" {
}
module "logo_upload_bucket" {
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.2.0"
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name

View File

@@ -3,7 +3,7 @@ terraform {
required_providers {
cloudfoundry = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.15.5"
version = "0.53.0"
}
}
@@ -12,7 +12,6 @@ terraform {
key = "admin.tfstate.sandbox"
encrypt = "true"
region = "us-gov-west-1"
profile = "notify-terraform-backend"
}
}

View File

@@ -3,7 +3,7 @@ terraform {
required_providers {
cloudfoundry = {
source = "cloudfoundry-community/cloudfoundry"
version = "~> 0.15"
version = "0.53.0"
}
}
}

View File

@@ -7,7 +7,7 @@ locals {
}
module "redis" {
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.2.0"
source = "github.com/18f/terraform-cloudgov//redis?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name
@@ -17,7 +17,7 @@ module "redis" {
}
module "logo_upload_bucket" {
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.2.0"
source = "github.com/18f/terraform-cloudgov//s3?ref=v0.7.1"
cf_org_name = local.cf_org_name
cf_space_name = local.cf_space_name

View File

@@ -3,7 +3,7 @@ terraform {
required_providers {
cloudfoundry = {
source = "cloudfoundry-community/cloudfoundry"
version = "0.15.5"
version = "0.53.0"
}
}
@@ -12,7 +12,6 @@ terraform {
key = "admin.tfstate.stage"
encrypt = "true"
region = "us-gov-west-1"
profile = "notify-terraform-backend"
}
}

View File

@@ -71,49 +71,32 @@ FAKE_ONE_OFF_NOTIFICATION = {
"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,
MOCK_JOBS = {
"data": [
{
"archived": False,
"created_at": "2024-01-04T20:43:52+00:00",
"created_by": {
"id": "mocked_user_id",
"name": "mocked_user",
},
"id": "mocked_notification_id",
"job_status": "finished",
"notification_count": 1,
"original_file_name": "mocked_file.csv",
"processing_finished": "2024-01-25T23:02:25+00:00",
"processing_started": "2024-01-25T23:02:24+00:00",
"scheduled_for": None,
"service": "21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3",
"service_name": {"name": "Mock Texting Service"},
"statistics": [{"count": 1, "status": "sending"}],
"template": "6a456418-498c-4c86-b0cd-9403c14a216c",
"template_name": "Mock Template Name",
"template_type": "sms",
"version": 1,
},
"to": "+12133166548",
"updated_at": "2024-01-04T20:43:53+00:00",
}
"template_version": 3,
"updated_at": "2024-01-25T23:02:25+00:00",
}
]
}
@@ -309,7 +292,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -725,7 +708,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,

View File

@@ -85,49 +85,32 @@ FAKE_ONE_OFF_NOTIFICATION = {
"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,
MOCK_JOBS = {
"data": [
{
"archived": False,
"created_at": "2024-01-04T20:43:52+00:00",
"created_by": {
"id": "mocked_user_id",
"name": "mocked_user",
},
"id": "mocked_notification_id",
"job_status": "finished",
"notification_count": 1,
"original_file_name": "mocked_file.csv",
"processing_finished": "2024-01-25T23:02:25+00:00",
"processing_started": "2024-01-25T23:02:24+00:00",
"scheduled_for": None,
"service": "21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3",
"service_name": {"name": "Mock Texting Service"},
"statistics": [{"count": 1, "status": "sending"}],
"template": "6a456418-498c-4c86-b0cd-9403c14a216c",
"template_name": "Mock Template Name",
"template_type": "sms",
"version": 1,
},
"to": "+12133166548",
"updated_at": "2024-01-04T20:43:53+00:00",
}
"template_version": 3,
"updated_at": "2024-01-25T23:02:25+00:00",
}
]
}
stub_template_stats = [
@@ -218,7 +201,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -248,7 +231,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -275,7 +258,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -303,7 +286,6 @@ 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,
@@ -312,7 +294,8 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_messages(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
)
mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID)
mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
banner = page.select("a.banner-dashboard")[1]
assert (
normalize_spaces(banner.text)
@@ -335,7 +318,6 @@ 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,
@@ -344,7 +326,8 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_no_messages(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
)
mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID)
mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
banner = page.select("a.banner-dashboard")[1]
assert normalize_spaces(banner.text) == "0 text messages received"
assert banner["href"] == url_for("main.inbox", service_id=SERVICE_ONE_ID)
@@ -585,7 +568,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -642,7 +625,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -782,7 +765,6 @@ 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,
@@ -792,7 +774,8 @@ def test_should_show_upcoming_jobs_on_dashboard(
service_id=SERVICE_ONE_ID,
)
mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID)
mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mock_get_scheduled_job_stats.assert_called_once_with(SERVICE_ONE_ID)
assert normalize_spaces(page.select_one("main h2").text) == ("In the next few days")
@@ -824,7 +807,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -850,7 +833,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -904,7 +887,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -938,7 +921,6 @@ 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,
@@ -947,7 +929,8 @@ def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
)
mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID)
mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
for filename in {
"export 1/1/2016.xls",
"all email addresses.xlsx",
@@ -992,8 +975,8 @@ def test_usage_page(
sms_column = normalize_spaces(annual_usage[0].text)
assert (
"You have sent 251,800 messages of your 250,000 free messages allowance. You have 0 messages remaining."
in sms_column
"You have sent 251,800 text message parts of your 250,000 free message parts allowance."
" You have 0 message parts remaining." in sms_column
)
assert "$29.85 spent" not in sms_column
assert "1,500 at 1.65 pence" not in sms_column
@@ -1029,8 +1012,8 @@ def test_usage_page_no_sms_spend(
annual_usage = page.find_all("div", {"class": "keyline-block"})
sms_column = normalize_spaces(annual_usage[0].text)
assert (
"You have sent 1,000 messages of your 250,000 free messages allowance. You have 249,000 messages remaining."
in sms_column
"You have sent 1,000 text message parts of your 250,000 free message parts allowance."
" You have 249,000 message parts remaining." in sms_column
)
assert "$0.00 spent" not in sms_column
assert "pence per message" not in sms_column
@@ -1106,7 +1089,7 @@ def test_usage_page_with_0_free_allowance(
sms_column = normalize_spaces(annual_usage[0].text)
assert (
"You have sent 251,800 messages of your 0 free messages allowance. You have"
"You have sent 251,800 text message parts of your 0 free message parts allowance. You have"
in sms_column
)
assert "free allowance remaining" not in sms_column
@@ -1209,7 +1192,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1246,7 +1229,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1283,7 +1266,7 @@ def test_menu_main_settings(
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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1319,7 +1302,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1359,7 +1342,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1404,7 +1387,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1460,7 +1443,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1586,7 +1569,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1653,7 +1636,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1689,7 +1672,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
@@ -1727,7 +1710,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
@@ -1763,7 +1746,7 @@ def test_breadcrumb_shows_if_service_is_suspended(
"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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
@@ -1797,7 +1780,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,
@@ -1840,7 +1823,7 @@ def test_service_dashboard_shows_free_allowance(
}
],
)
mocker.patch("app.job_api_client.get_job", return_value=MOCK_ONE_OFF_JOB)
mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
@@ -1864,7 +1847,7 @@ def test_service_dashboard_shows_batched_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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",
return_value=FAKE_ONE_OFF_NOTIFICATION,

View File

@@ -349,9 +349,10 @@ def test_should_show_scheduled_job(
job_id=fake_uuid,
)
assert normalize_spaces(page.select("main p")[1].text) == (
assert normalize_spaces(page.select("main div p")[1].text) == (
"Sending Two week reminder today at 00:00 US/Eastern"
)
assert page.select("main p a")[0]["href"] == url_for(
"main.view_template_version",
service_id=SERVICE_ONE_ID,
@@ -481,3 +482,22 @@ def test_should_show_updates_for_scheduled_job_as_json(
@freeze_time("2016-01-10 12:00:00.000000")
def test_time_left(job_created_at, expected_message):
assert get_time_left(job_created_at) == expected_message
def test_should_show_message_note(
client_request,
mock_get_service_template,
mock_get_scheduled_job,
mock_get_service_data_retention,
mock_get_notifications,
fake_uuid,
):
page = client_request.get(
"main.view_job",
service_id=SERVICE_ONE_ID,
job_id=fake_uuid,
)
assert normalize_spaces(page.select_one("main p.notification-status").text) == (
"Messages will remain in pending state until carrier status is received, typically 5 minutes."
)

View File

@@ -57,49 +57,32 @@ FAKE_ONE_OFF_NOTIFICATION = {
"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,
MOCK_JOBS = {
"data": [
{
"archived": False,
"created_at": "2024-01-04T20:43:52+00:00",
"created_by": {
"id": "mocked_user_id",
"name": "mocked_user",
},
"id": "mocked_notification_id",
"job_status": "finished",
"notification_count": 1,
"original_file_name": "mocked_file.csv",
"processing_finished": "2024-01-25T23:02:25+00:00",
"processing_started": "2024-01-25T23:02:24+00:00",
"scheduled_for": None,
"service": "21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3",
"service_name": {"name": "Mock Texting Service"},
"statistics": [{"count": 1, "status": "sending"}],
"template": "6a456418-498c-4c86-b0cd-9403c14a216c",
"template_name": "Mock Template Name",
"template_type": "sms",
"version": 1,
},
"to": "+12133166548",
"updated_at": "2024-01-04T20:43:53+00:00",
}
"template_version": 3,
"updated_at": "2024-01-25T23:02:25+00:00",
}
]
}
@@ -139,7 +122,7 @@ 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.job_api_client.get_jobs", return_value=MOCK_JOBS)
mocker.patch(
"app.notification_api_client.get_notifications_for_service",

View File

@@ -1955,7 +1955,7 @@ def test_set_template_sender(
"sms",
False,
"a" * 160,
"Will be charged as 2 text messages",
"Will be charged as 1 text message",
None,
),
(
@@ -1969,7 +1969,7 @@ def test_set_template_sender(
"sms",
True,
"a" * 147,
"Will be charged as 2 text messages",
"Will be charged as 1 text message",
None,
),
(
@@ -1984,7 +1984,7 @@ def test_set_template_sender(
"sms",
False,
"a" * 918,
"Will be charged as 7 text messages",
"Will be charged as 6 text messages",
None,
),
(
@@ -2025,11 +2025,8 @@ def test_set_template_sender(
(
"sms",
False,
# The length of this string in bytes is 210, needing two fragments.
# Seems like previous calculation was wrong unless the number of characters
# somehow has priority over the number of bytes in a fragment (?)
"" * 70,
"Will be charged as 2 text messages",
"Will be charged as 1 text message. Use of characters outside the IEC_8859-1 character set may increase the message fragment count, resulting in additional charges, and these IEC_8859-1 characters may not display properly on some older mobile devices.", # noqa E501
None,
),
(
@@ -2043,11 +2040,7 @@ def test_set_template_sender(
"sms",
False,
"" * 918,
# The length of this string in bytes is 2754. Divide by 140 and we get 19. Then round up.
# Don't know why it was previously calculated as 14. They seem to have charged by characters
# rather than length of the fragment in bytes.
# "Will be charged as 14 text messages",
"Will be charged as 20 text messages",
"Will be charged as 14 text messages",
None,
),
(

View File

@@ -91,14 +91,14 @@ def get_notifications_csv_mock(
None,
[
"Recipient,Template,Type,Sent by,Job,Carrier,Carrier Response,Status,Time\n",
"foo@bar.com,foo,sms,,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 US/Eastern\r\n",
"foo@bar.com,foo,sms,,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n",
],
),
(
"Anne Example",
[
"Recipient,Template,Type,Sent by,Job,Carrier,Carrier Response,Status,Time\n",
"foo@bar.com,foo,sms,Anne Example,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 US/Eastern\r\n", # noqa
"foo@bar.com,foo,sms,Anne Example,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n", # noqa
],
),
],
@@ -151,7 +151,7 @@ def test_generate_notifications_csv_without_job(
"ATT Mobility",
"Did not like it",
"Delivered",
"1943-04-19 08:00:00 US/Eastern",
"1943-04-19 08:00:00 AM US/Eastern",
],
),
(
@@ -187,7 +187,7 @@ def test_generate_notifications_csv_without_job(
"ATT Mobility",
"Did not like it",
"Delivered",
"1943-04-19 08:00:00 US/Eastern",
"1943-04-19 08:00:00 AM US/Eastern",
],
),
(
@@ -223,7 +223,7 @@ def test_generate_notifications_csv_without_job(
"ATT Mobility",
"Did not like it",
"Delivered",
"1943-04-19 08:00:00 US/Eastern",
"1943-04-19 08:00:00 AM US/Eastern",
],
),
],
@@ -398,4 +398,4 @@ def test_get_errors_for_csv(
def test_convert_report_date_to_preferred_timezone():
original = "2023-11-16 05:00:00"
altered = convert_report_date_to_preferred_timezone(original)
assert altered == "2023-11-16 00:00:00 US/Eastern"
assert altered == "2023-11-16 12:00:00 AM US/Eastern"