diff --git a/app/assets/js/setTimezone.js b/app/assets/js/setTimezone.js new file mode 100644 index 0000000000..5a79b0190e --- /dev/null +++ b/app/assets/js/setTimezone.js @@ -0,0 +1,4 @@ +document.addEventListener('DOMContentLoaded', function() { + var timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + document.cookie = `timezone=${timeZone}; path=/`; +}) diff --git a/app/main/views/dashboard.py b/app/main/views/dashboard.py index 0913dcfdda..ee289c3e5d 100644 --- a/app/main/views/dashboard.py +++ b/app/main/views/dashboard.py @@ -16,6 +16,7 @@ ) from app.formatters import format_date_numeric, format_datetime_numeric, get_time_left from app.main import main +from app.main.views.user_profile import set_timezone from app.statistics_utils import get_formatted_percentage from app.utils import ( DELIVERED_STATUSES, @@ -39,6 +40,7 @@ def old_service_dashboard(service_id): @main.route("/services/") @user_has_permissions() def service_dashboard(service_id): + if session.get("invited_user_id"): session.pop("invited_user_id", None) session["service_id"] = service_id @@ -402,11 +404,13 @@ def get_dashboard_partials(service_id): def get_dashboard_totals(statistics): + for msg_type in statistics.values(): msg_type["failed_percentage"] = get_formatted_percentage( msg_type["failed"], msg_type["requested"] ) msg_type["show_warning"] = float(msg_type["failed_percentage"]) > 3 + return statistics @@ -465,6 +469,8 @@ def get_months_for_financial_year(year, time_format="%B"): def get_current_month_for_financial_year(year): + # Setting the timezone here because we need to set it somewhere. + set_timezone() current_month = datetime.now().month return current_month diff --git a/app/main/views/send.py b/app/main/views/send.py index 6f2817826f..e6843da18d 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -33,6 +33,7 @@ SetSenderForm, get_placeholder_form_instance, ) +from app.main.views.user_profile import set_timezone from app.models.user import Users from app.s3_client.s3_csv_client import ( get_csv_metadata, @@ -1075,6 +1076,7 @@ def get_spreadsheet_column_headings_from_template(template): def get_recipient(): + set_timezone() if {"recipient", "placeholders"} - set(session.keys()): return None diff --git a/app/main/views/user_profile.py b/app/main/views/user_profile.py index 2efac8b236..ec9e27035f 100644 --- a/app/main/views/user_profile.py +++ b/app/main/views/user_profile.py @@ -29,6 +29,7 @@ TwoFactorForm, ) from app.models.user import User +from app.utils import hilite from app.utils.user import user_is_gov_user, user_is_logged_in from notifications_utils.url_safe_token import check_token @@ -275,3 +276,15 @@ def user_profile_disable_platform_admin_view(): return render_template( "views/user-profile/disable-platform-admin-view.html", form=form ) + + +def set_timezone(): + # Cookie is set in dashboard.html on page load + try: + timezone = request.cookies.get("timezone", "US/Eastern") + current_app.logger.debug(hilite(f"User's timezone is {timezone}")) + serialized_user = current_user.serialize() + if serialized_user["preferred_timezone"] is not timezone: + current_user.update(preferred_timezone=timezone) + except Exception: + current_app.logger.exception(hilite("Can't get timezone")) diff --git a/app/models/user.py b/app/models/user.py index 3c96fc42c4..6991dc035b 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -58,6 +58,7 @@ def __init__(self, _dict): super().__init__(_dict) self.permissions = _dict.get("permissions", {}) self._platform_admin = _dict["platform_admin"] + self.preferred_timezone = _dict.get("preferred_timezone", "America/New_York") @classmethod def from_id(cls, user_id): diff --git a/app/templates/views/dashboard/dashboard.html b/app/templates/views/dashboard/dashboard.html index 3ea625bb37..9268b37e33 100644 --- a/app/templates/views/dashboard/dashboard.html +++ b/app/templates/views/dashboard/dashboard.html @@ -1,3 +1,5 @@ + + {% extends "withnav_template.html" %} {% from "components/table.html" import list_table, field, text_field, link_field, right_aligned_field_heading, hidden_field_heading, row_heading, notification_status_field, notification_carrier_field, notification_carrier_message_field %} @@ -8,6 +10,8 @@ {% endblock %} {% block maincolumn_content %} + +
diff --git a/app/templates/views/send.html b/app/templates/views/send.html index 8685f81968..b8a0c65610 100644 --- a/app/templates/views/send.html +++ b/app/templates/views/send.html @@ -4,6 +4,7 @@ {% from "components/file-upload.html" import file_upload %} {% from "components/table.html" import list_table, text_field, index_field, index_field_heading %} {% from "components/components/back-link/macro.njk" import usaBackLink %} + {% block service_page_title %} Upload a list of {{ 999|recipient_count_label(template.template_type) }} diff --git a/app/templates/views/user-profile.html b/app/templates/views/user-profile.html index 6f243d7a82..246444d5a1 100644 --- a/app/templates/views/user-profile.html +++ b/app/templates/views/user-profile.html @@ -45,14 +45,9 @@

User profile

{% endcall %} {% call row() %} - {{ text_field('Preferred Timezone') }} + {{ text_field('Preferred timezone') }} {{ optional_text_field(current_user.preferred_timezone) }} - {{ edit_field( - 'Change', - url_for('.user_profile_preferred_timezone'), - suffix='preferred timezone' - ) - }} + {{ text_field('Set automatically') }} {% endcall %} {% if current_user.platform_admin or session.get('disable_platform_admin_view') %} diff --git a/gulpfile.js b/gulpfile.js index d4d6ac81f4..4d2571eaec 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -97,6 +97,13 @@ const copyGtmHead = () => { .pipe(dest(paths.dist + 'js/')); }; +// Task to copy `setTimezone.js` +const copySetTimezone = () => { + return src(paths.src + 'js/setTimezone.js') + .pipe(dest(paths.dist + 'js/')); +}; + + // Task to copy images const copyImages = () => { return src(paths.src + 'images/**/*', { encoding: false }) @@ -122,4 +129,4 @@ const copyAssets = async () => { await uswds.copyAssets(); }; -exports.default = series(styles, javascripts, copyGtmHead, copyImages, copyAssets); +exports.default = series(styles, javascripts, copyGtmHead, copySetTimezone, copyImages, copyAssets); diff --git a/poetry.lock b/poetry.lock index 4cc5704710..2684ecc215 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2433,7 +2433,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, diff --git a/tests/app/main/views/test_conversation.py b/tests/app/main/views/test_conversation.py index 1139cd43f8..15a5ec587c 100644 --- a/tests/app/main/views/test_conversation.py +++ b/tests/app/main/views/test_conversation.py @@ -113,55 +113,55 @@ def test_view_conversation( [ ( "message-8", - "yesterday at 14:59 US/Eastern", + "yesterday at 14:59 America/New_York", ), ( "message-7", - "yesterday at 14:59 US/Eastern", + "yesterday at 14:59 America/New_York", ), ( "message-6", - "yesterday at 16:59 US/Eastern", + "yesterday at 16:59 America/New_York", ), ( "message-5", - "yesterday at 18:59 US/Eastern", + "yesterday at 18:59 America/New_York", ), ( "message-4", - "yesterday at 20:59 US/Eastern", + "yesterday at 20:59 America/New_York", ), ( "message-3", - "yesterday at 22:59 US/Eastern", + "yesterday at 22:59 America/New_York", ), ( "message-2", - "yesterday at 22:59 US/Eastern", + "yesterday at 22:59 America/New_York", ), ( "message-1", - "yesterday at 23:00 US/Eastern", + "yesterday at 23:00 America/New_York", ), ( expected_outbound_content, - "yesterday at 00:00 US/Eastern", + "yesterday at 00:00 America/New_York", ), ( expected_outbound_content, - "yesterday at 00:00 US/Eastern", + "yesterday at 00:00 America/New_York", ), ( expected_outbound_content, - "yesterday at 00:00 US/Eastern", + "yesterday at 00:00 America/New_York", ), ( expected_outbound_content, - "yesterday at 00:00 US/Eastern", + "yesterday at 00:00 America/New_York", ), ( expected_outbound_content, - "yesterday at 00:00 US/Eastern", + "yesterday at 00:00 America/New_York", ), ] ): diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index d5bab63232..e2a7a6b587 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -550,14 +550,14 @@ def test_download_inbox( ) assert response.get_data(as_text=True) == ( "Phone number,Message,Received\r\n" - "(202) 867-5300,message-1,07-01-2016 11:00 US/Eastern\r\n" - "(202) 867-5300,message-2,07-01-2016 10:59 US/Eastern\r\n" - "(202) 867-5300,message-3,07-01-2016 10:59 US/Eastern\r\n" - "(202) 867-5302,message-4,07-01-2016 08:59 US/Eastern\r\n" - "+33(0)1 12345678,message-5,07-01-2016 06:59 US/Eastern\r\n" - "(202) 555-0104,message-6,07-01-2016 04:59 US/Eastern\r\n" - "(202) 555-0104,message-7,07-01-2016 02:59 US/Eastern\r\n" - "+68212345,message-8,07-01-2016 02:59 US/Eastern\r\n" + "(202) 867-5300,message-1,07-01-2016 11:00 America/New_York\r\n" + "(202) 867-5300,message-2,07-01-2016 10:59 America/New_York\r\n" + "(202) 867-5300,message-3,07-01-2016 10:59 America/New_York\r\n" + "(202) 867-5302,message-4,07-01-2016 08:59 America/New_York\r\n" + "+33(0)1 12345678,message-5,07-01-2016 06:59 America/New_York\r\n" + "(202) 555-0104,message-6,07-01-2016 04:59 America/New_York\r\n" + "(202) 555-0104,message-7,07-01-2016 02:59 America/New_York\r\n" + "+68212345,message-8,07-01-2016 02:59 America/New_York\r\n" ) @@ -849,7 +849,7 @@ def test_should_show_upcoming_jobs_on_dashboard( assert normalize_spaces(page.select_one("main h2").text) == ("In the next few days") assert normalize_spaces(page.select_one("a.banner-dashboard").text) == ( - "2 files waiting to send " "- sending starts today at 06:09 US/Eastern" + "2 files waiting to send " "- sending starts today at 06:09 America/New_York" ) assert page.select_one("a.banner-dashboard")["href"] == url_for( @@ -1786,7 +1786,6 @@ def test_org_breadcrumbs_show_if_user_is_platform_admin( client_request.login(platform_admin_user, service_one_json) page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID) - assert page.select_one(".navigation-organization-link")["href"] == url_for( "main.organization_dashboard", org_id=ORGANISATION_ID, diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index c4a7561946..ab09f7ecd8 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -349,7 +349,7 @@ def test_should_show_scheduled_job( ) assert normalize_spaces(page.select("main div p")[1].text) == ( - "Example template - service one was scheduled on January 02, 2016 at 12:00 AM US/Eastern by Test User" + "Example template - service one was scheduled on January 02, 2016 at 12:00 AM America/New_York by Test User" ) assert page.select("main p a")[0]["href"] == url_for( diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index b23caf925b..6e2ff227de 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -497,7 +497,7 @@ def test_caseworker_sees_template_page_if_template_is_deleted( ) assert ( page.select("p.hint")[0].text.strip() - == "This template was deleted today at 15:00 US/Eastern." + == "This template was deleted today at 15:00 America/New_York." ) mock_get_deleted_template.assert_called_with(SERVICE_ONE_ID, template_id, None) @@ -1591,7 +1591,7 @@ def test_should_show_page_for_a_deleted_template( ) assert ( page.select("p.hint")[0].text.strip() - == "This template was deleted today at 15:00 US/Eastern." + == "This template was deleted today at 15:00 America/New_York." ) assert "Delete this template" not in page.select_one("main").text diff --git a/tests/app/main/views/uploads/test_upload_hub.py b/tests/app/main/views/uploads/test_upload_hub.py index ee24fba653..53ba1f161a 100644 --- a/tests/app/main/views/uploads/test_upload_hub.py +++ b/tests/app/main/views/uploads/test_upload_hub.py @@ -52,7 +52,7 @@ def test_get_upload_hub_page( assert normalize_spaces(uploads[0].text.strip()) == ( "some.csv " - "Sent 1 January 2016 at 06:09 US/Eastern " + "Sent 1 January 2016 at 06:09 America/New_York " "0 pending 8 delivered 2 failed" ) assert uploads[0].select_one("a.file-list-filename-large")["href"] == ( @@ -82,12 +82,12 @@ def test_uploads_page_shows_scheduled_jobs( ("File Status"), ( "even_later.csv " - "Sending 1 January 2016 at 18:09 US/Eastern " + "Sending 1 January 2016 at 18:09 America/New_York " "1 text message waiting to send" ), ( "send_me_later.csv " - "Sending 1 January 2016 at 06:09 US/Eastern " + "Sending 1 January 2016 at 06:09 America/New_York " "1 text message waiting to send" ), ] diff --git a/tests/app/models/test_user.py b/tests/app/models/test_user.py index 1caf85f4a5..099cab14d0 100644 --- a/tests/app/models/test_user.py +++ b/tests/app/models/test_user.py @@ -18,6 +18,7 @@ def test_user(notify_admin): "email_address": "test@user.gsa.gov", "mobile_number": "+12021231234", "state": "pending", + "preferred_timezone": "America/Chicago", "failed_login_count": 0, "platform_admin": False, }