diff --git a/Makefile b/Makefile index 21e39e844..7085bee58 100644 --- a/Makefile +++ b/Makefile @@ -82,7 +82,7 @@ dead-code: .PHONY: e2e-test e2e-test: export NEW_RELIC_ENVIRONMENT=test e2e-test: ## Run end-to-end integration tests; note that --browser webkit isn't currently working - poetry run pytest -v --browser chromium --browser firefox tests/end_to_end + poetry run pytest -vv --browser chromium --browser firefox tests/end_to_end .PHONY: js-lint js-lint: ## Run javascript linting scanners diff --git a/app/__init__.py b/app/__init__.py index 5292c767b..d2c61d0a0 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -52,6 +52,7 @@ from app.formatters import ( format_datetime_human, format_datetime_normal, format_datetime_relative, + format_datetime_scheduled_notification, format_datetime_table, format_day_of_week, format_delta, @@ -551,6 +552,7 @@ def add_template_filters(application): format_datetime, format_datetime_24h, format_datetime_normal, + format_datetime_scheduled_notification, format_datetime_table, valid_phone_number, linkable_name, diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index 4a909e123..d66e276bd 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -583,3 +583,9 @@ details form { #countdown-container { display: none; // Hide the countdown timer } + +.placeholder, .placeholder-conditional { + background-color: #face00; + border: 1px solid #face00; + border-radius: 7px; +} diff --git a/app/formatters.py b/app/formatters.py index 29cb9a4ed..23ef6690a 100644 --- a/app/formatters.py +++ b/app/formatters.py @@ -23,7 +23,7 @@ from notifications_utils.recipients import InvalidPhoneError, validate_phone_num from notifications_utils.take import Take from app.utils.csv import get_user_preferred_timezone -from app.utils.time import parse_dt, parse_naive_dt +from app.utils.time import parse_naive_dt def apply_html_class(tags, html_file): @@ -94,16 +94,34 @@ def format_datetime_normal(date): ) +def format_datetime_scheduled_notification(date): + # e.g. April 09, 2024 at 04:00 PM US/Eastern. + # Everything except scheduled notifications, the time is always "now". + # Scheduled notifications are the exception to the rule. + # Here we are formating and displaying the datetime without converting datetime to a different timezone. + + datetime_obj = parse_naive_dt(date) + + format_time_without_tz = datetime_obj.replace(tzinfo=timezone.utc).strftime( + "%I:%M %p" + ) + return "{} at {} {}".format( + format_date_normal(date), format_time_without_tz, get_user_preferred_timezone() + ) + + def format_datetime_table(date): # example: 03-18-2024 at 04:53 PM, intended for datetimes in tables return "{} at {}".format(format_date_numeric(date), format_time_12h(date)) def format_time_12h(date): - date = parse_dt(date) + date = parse_naive_dt(date) preferred_tz = pytz.timezone(get_user_preferred_timezone()) - return date.astimezone(preferred_tz).strftime("%I:%M %p") + return ( + date.replace(tzinfo=timezone.utc).astimezone(preferred_tz).strftime("%I:%M %p") + ) def format_datetime_relative(date): diff --git a/app/main/views/platform_admin.py b/app/main/views/platform_admin.py index 802ed5b18..6190ac988 100644 --- a/app/main/views/platform_admin.py +++ b/app/main/views/platform_admin.py @@ -662,7 +662,21 @@ def create_global_stats(services): "email": {"delivered": 0, "failed": 0, "requested": 0}, "sms": {"delivered": 0, "failed": 0, "requested": 0}, } + # Issue #1323. The back end is now sending 'failure' instead of + # 'failed'. Adjust it here, but keep it flexible in case + # the backend reverts to 'failed'. for service in services: + if service["statistics"]["sms"].get("failure") is not None: + service["statistics"]["sms"]["failed"] = service["statistics"]["sms"][ + "failure" + ] + if service["statistics"]["email"].get("failure") is not None: + service["statistics"]["email"]["failed"] = service["statistics"]["email"][ + "failure" + ] + + for service in services: + for msg_type, status in itertools.product( ("sms", "email"), ("delivered", "failed", "requested") ): diff --git a/app/main/views/send.py b/app/main/views/send.py index 3aa5bd830..02f7d2121 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -470,7 +470,7 @@ def send_one_off_step(service_id, template_id, step_index): ) -def _check_messages(service_id, template_id, upload_id, preview_row): +def _check_messages(service_id, template_id, upload_id, preview_row, **kwargs): try: # The happy path is that the job doesn’t already exist, so the # API will return a 404 and the client will raise HTTPError. @@ -510,11 +510,7 @@ def _check_messages(service_id, template_id, upload_id, preview_row): show_recipient=False, email_reply_to=email_reply_to, sms_sender=sms_sender, - ) - simplifed_template = get_template( - db_template, - current_service, - show_recipient=False, + **kwargs, ) allow_list = [] @@ -535,7 +531,7 @@ def _check_messages(service_id, template_id, upload_id, preview_row): allow_list = None recipients = RecipientCSV( contents, - template=template or simplifed_template, + template=template, max_initial_rows_shown=50, max_errors_shown=50, guestlist=allow_list, @@ -569,9 +565,6 @@ def _check_messages(service_id, template_id, upload_id, preview_row): if preview_row < len(recipients) + 2: template.values = recipients[preview_row - 2].recipient_and_personalisation - simplifed_template.values = recipients[ - preview_row - 2 - ].recipient_and_personalisation elif preview_row > 2: abort(404) @@ -599,7 +592,6 @@ def _check_messages(service_id, template_id, upload_id, preview_row): service_id, template.id, db_template["version"], original_file_name ), template_id=template_id, - simplifed_template=simplifed_template, ) @@ -658,7 +650,9 @@ def check_messages(service_id, template_id, upload_id, row_index=2): @user_has_permissions("send_messages", restrict_admin_usage=True) def preview_job(service_id, template_id, upload_id, row_index=2): session["scheduled_for"] = request.form.get("scheduled_for", "") - data = _check_messages(service_id, template_id, upload_id, row_index) + data = _check_messages( + service_id, template_id, upload_id, row_index, force_hide_sender=True + ) return render_template( "views/check/preview.html", @@ -825,11 +819,11 @@ def send_one_off_to_myself(service_id, template_id): def check_notification(service_id, template_id): return render_template( "views/notifications/check.html", - **_check_notification(service_id, template_id), + **_check_notification(service_id, template_id, show_recipient=True), ) -def _check_notification(service_id, template_id, exception=None): +def _check_notification(service_id, template_id, exception=None, **kwargs): db_template = current_service.get_template_with_user_permission_or_403( template_id, current_user ) @@ -842,13 +836,9 @@ def _check_notification(service_id, template_id, exception=None): template = get_template( db_template, current_service, - show_recipient=True, email_reply_to=email_reply_to, sms_sender=sms_sender, - ) - simplifed_template = get_template( - db_template, - current_service, + **kwargs, ) placeholders = fields_to_fill_in(template) @@ -874,7 +864,6 @@ def _check_notification(service_id, template_id, exception=None): back_link_from_preview=back_link_from_preview, choose_time_form=choose_time_form, **(get_template_error_dict(exception) if exception else {}), - simplifed_template=simplifed_template, ) @@ -924,7 +913,9 @@ def preview_notification(service_id, template_id): return render_template( "views/notifications/preview.html", - **_check_notification(service_id, template_id), + **_check_notification( + service_id, template_id, show_recipient=False, force_hide_sender=True + ), scheduled_for=session["scheduled_for"], recipient=recipient, ) diff --git a/app/templates/views/check/preview.html b/app/templates/views/check/preview.html index 393516336..32b158013 100644 --- a/app/templates/views/check/preview.html +++ b/app/templates/views/check/preview.html @@ -21,14 +21,14 @@ {{ page_header('Preview') }}
-

Scheduled: {{ scheduled_for |format_datetime_normal if scheduled_for else 'Now'}}

+

Scheduled: {{ scheduled_for |format_datetime_scheduled_notification if scheduled_for else 'Now'}}

File: {{original_file_name}}

Template: {{template.name}}

From: {{ template.sender }}

Message

-
{{ simplifed_template|string }}
+
{{ template|string }}
{% if not request.args.from_test %}

Recipients list

diff --git a/app/templates/views/notifications/preview.html b/app/templates/views/notifications/preview.html index e07435c1a..ce94a606f 100644 --- a/app/templates/views/notifications/preview.html +++ b/app/templates/views/notifications/preview.html @@ -43,14 +43,14 @@ {{ page_header('Preview') }} {% endif %}
-

Scheduled: {{ scheduled_for |format_datetime_normal if scheduled_for else 'Now'}}

+

Scheduled: {{ scheduled_for |format_datetime_scheduled_notification if scheduled_for else 'Now'}}

Template: {{template.name}}

From: {{ template.sender }}

To: {{ recipient }}

Message

-
{{ simplifed_template|string }}
+
{{ template|string }}
0: + handle_no_existing_template_case(page) + else: + handle_existing_template_case(page) + + _teardown(page) + + +def _teardown(page): + page.click("text='Settings'") + + # Check to make sure that we've arrived at the next page. + page.wait_for_load_state("domcontentloaded") + + page.click("text='Delete this service'") + + # Check to make sure that we've arrived at the next page. + page.wait_for_load_state("domcontentloaded") + + page.click("text='Yes, delete'") + + # Check to make sure that we've arrived at the next page. + page.wait_for_load_state("domcontentloaded") + + # Check to make sure that we've arrived at the next page. + # Check the page title exists and matches what we expect. + expect(page).to_have_title(re.compile("Choose service"))