diff --git a/.ds.baseline b/.ds.baseline index 8b09a97f4..4f2a73d8c 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -161,7 +161,7 @@ "filename": "app/config.py", "hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc", "is_verified": false, - "line_number": 119, + "line_number": 124, "is_secret": false } ], @@ -634,5 +634,5 @@ } ] }, - "generated_at": "2025-07-31T17:38:47Z" + "generated_at": "2025-07-31T11:53:28Z" } diff --git a/app/assets/javascripts/activityChart.js b/app/assets/javascripts/activityChart.js index e2add6117..4fa8922ec 100644 --- a/app/assets/javascripts/activityChart.js +++ b/app/assets/javascripts/activityChart.js @@ -316,9 +316,18 @@ return rowSender === currentUserName; }); - userRows.slice(0, 5).forEach(row => { - row.style.display = ''; - }); + if (userRows.length > 0) { + userRows.slice(0, 5).forEach(row => { + row.style.display = ''; + }); + } else { + const emptyMessageRow = Array.from(allRows).find(row => { + return row.querySelector('.table-empty-message'); + }); + if (emptyMessageRow) { + emptyMessageRow.style.display = ''; + } + } } else { tableHeading.textContent = 'Service activity'; diff --git a/app/assets/javascripts/listEntry.js b/app/assets/javascripts/listEntry.js index a2c0df0a3..f18b0c94f 100644 --- a/app/assets/javascripts/listEntry.js +++ b/app/assets/javascripts/listEntry.js @@ -17,6 +17,7 @@ this.minEntries = 2; this.listItemName = this.$wrapper.data('listItemName'); this.getSharedAttributes(); + this.getOriginalClasses(); this.getValues(); this.maxEntries = this.entries.length; @@ -28,17 +29,18 @@ ListEntry.prototype.renderEntry = function(data) { return `
-
diff --git a/app/templates/views/service-settings/data-retention/add.html b/app/templates/views/service-settings/data-retention/add.html index bd20e5f7e..3c9499d69 100644 --- a/app/templates/views/service-settings/data-retention/add.html +++ b/app/templates/views/service-settings/data-retention/add.html @@ -3,6 +3,7 @@ {% from "components/page-footer.html" import page_footer %} {% from "components/form.html" import form_wrapper %} {% from "components/components/back-link/macro.njk" import usaBackLink %} +{% from "components/radios.html" import radios %} {% block service_page_title %} Set data retention @@ -16,7 +17,9 @@ {{ page_header('Set data retention') }} {% call form_wrapper() %} - {{ form.notification_type }} +
+ {{ radios(form.notification_type) }} +
{{ form.days_of_retention }} {{ page_footer('Add') }} {% endcall %} diff --git a/app/templates/views/service-settings/link-service-to-organization.html b/app/templates/views/service-settings/link-service-to-organization.html index 222c3951f..8d847a333 100644 --- a/app/templates/views/service-settings/link-service-to-organization.html +++ b/app/templates/views/service-settings/link-service-to-organization.html @@ -4,6 +4,7 @@ {% from "components/live-search.html" import live_search %} {% from "components/form.html" import form_wrapper %} {% from "components/components/back-link/macro.njk" import usaBackLink %} +{% from "components/radios.html" import radios %} {% set page_title = "Link service to organization" %} @@ -26,7 +27,9 @@ ) }} {% call form_wrapper(data_force_focus=True) %} {% if has_organizations %} - {{ form.organizations }} +
+ {{ radios(form.organizations) }} +
{{ sticky_page_footer('Save') }} {% else %}

No organizations

diff --git a/app/templates/views/set-up-your-profile.html b/app/templates/views/set-up-your-profile.html index e131aff2f..9a0e11cfc 100644 --- a/app/templates/views/set-up-your-profile.html +++ b/app/templates/views/set-up-your-profile.html @@ -2,6 +2,7 @@ {% from "components/page-footer.html" import page_footer %} {% from "components/form.html" import form_wrapper %} {% from "components/components/select/macro.njk" import usaSelect -%} +{% from "components/radios.html" import radios %} {% block per_page_title %} Set up your profile @@ -68,7 +69,7 @@ Set up your profile ] }) }}--> - {{form.auth_type}} + {{ radios(form.auth_type) }} {{ page_footer("Save") }} {% endcall %} diff --git a/app/templates/views/templates/_template_list.html b/app/templates/views/templates/_template_list.html index 6fc0baeec..e29131473 100644 --- a/app/templates/views/templates/_template_list.html +++ b/app/templates/views/templates/_template_list.html @@ -1,3 +1,5 @@ +{% from "components/checkboxes.html" import checkboxes %} + {% macro format_item_name(name, separators=True) -%} {%- if name is string -%} {{- name -}} diff --git a/app/templates/views/templates/set-sender.html b/app/templates/views/templates/set-sender.html index 2bb9a4162..293b8e847 100644 --- a/app/templates/views/templates/set-sender.html +++ b/app/templates/views/templates/set-sender.html @@ -2,6 +2,7 @@ {% from "components/page-footer.html" import page_footer %} {% from "components/form.html" import form_wrapper %} {% from "components/components/back-link/macro.njk" import usaBackLink %} +{% from "components/radios.html" import radios %} {% block service_page_title %} {{ sender_context.title }} @@ -15,20 +16,11 @@
{% call form_wrapper() %} - {{ form.sender(param_extensions={ - 'fieldset': { - 'legend': { - 'isPageHeading': True, - 'text': sender_context.title - } - }, - 'classes': 'grid-col-9' - }) }} + {{ radios(form.sender, legend_style='usa-legend--large', option_hints=option_hints) }}
{{ page_footer('Continue') }}
{% endcall %} -
{% endblock %} diff --git a/app/templates/views/templates/set-template-sender.html b/app/templates/views/templates/set-template-sender.html index 289ff222f..49600a860 100644 --- a/app/templates/views/templates/set-template-sender.html +++ b/app/templates/views/templates/set-template-sender.html @@ -2,6 +2,7 @@ {% from "components/page-footer.html" import page_footer %} {% from "components/form.html" import form_wrapper %} {% from "components/components/back-link/macro.njk" import usaBackLink %} +{% from "components/radios.html" import radios %} {% set page_title = 'Set letter contact block' %} @@ -17,19 +18,10 @@
{% call form_wrapper() %} - {{ form.sender(param_extensions={ - 'fieldset': { - 'legend': { - 'isPageHeading': True, - 'text': page_title - } - }, - 'classes': 'grid-col-9' - }) }} + {{ radios(form.sender) }}
{{ page_footer('Continue') }} - Add new sender -
+
{% endcall %} diff --git a/backstop_data/bitmaps_reference/backstop_test_API_Guest_List_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_API_Guest_List_0_document_0_desktop.png new file mode 100644 index 000000000..baa9e2d02 Binary files /dev/null and b/backstop_data/bitmaps_reference/backstop_test_API_Guest_List_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_API_Keys_Create_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_API_Keys_Create_0_document_0_desktop.png index 26cbd5605..13ae82a9a 100644 Binary files a/backstop_data/bitmaps_reference/backstop_test_API_Keys_Create_0_document_0_desktop.png and b/backstop_data/bitmaps_reference/backstop_test_API_Keys_Create_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_About_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_About_0_document_0_desktop.png index d79bc4d54..c4154f252 100644 Binary files a/backstop_data/bitmaps_reference/backstop_test_About_0_document_0_desktop.png and b/backstop_data/bitmaps_reference/backstop_test_About_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Change_User_Auth_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Change_User_Auth_0_document_0_desktop.png index b2e7c30fc..bb470a095 100644 Binary files a/backstop_data/bitmaps_reference/backstop_test_Change_User_Auth_0_document_0_desktop.png and b/backstop_data/bitmaps_reference/backstop_test_Change_User_Auth_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Data_Retention_Add_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Data_Retention_Add_0_document_0_desktop.png new file mode 100644 index 000000000..f8dea36d1 Binary files /dev/null and b/backstop_data/bitmaps_reference/backstop_test_Data_Retention_Add_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Email_Not_Received_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Email_Not_Received_0_document_0_desktop.png index d65b61da3..cb1d73de2 100644 Binary files a/backstop_data/bitmaps_reference/backstop_test_Email_Not_Received_0_document_0_desktop.png and b/backstop_data/bitmaps_reference/backstop_test_Email_Not_Received_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Invite_User_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Invite_User_0_document_0_desktop.png index f59850299..7107461be 100644 Binary files a/backstop_data/bitmaps_reference/backstop_test_Invite_User_0_document_0_desktop.png and b/backstop_data/bitmaps_reference/backstop_test_Invite_User_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Link_Service_to_Organization_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Link_Service_to_Organization_0_document_0_desktop.png new file mode 100644 index 000000000..73a73d913 Binary files /dev/null and b/backstop_data/bitmaps_reference/backstop_test_Link_Service_to_Organization_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Notifygov_Sign_In_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Notifygov_Sign_In_0_document_0_desktop.png index ddeb38cbe..6d970c703 100644 Binary files a/backstop_data/bitmaps_reference/backstop_test_Notifygov_Sign_In_0_document_0_desktop.png and b/backstop_data/bitmaps_reference/backstop_test_Notifygov_Sign_In_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Platform_Admin_User_Auth_Type_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Platform_Admin_User_Auth_Type_0_document_0_desktop.png new file mode 100644 index 000000000..bb470a095 Binary files /dev/null and b/backstop_data/bitmaps_reference/backstop_test_Platform_Admin_User_Auth_Type_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Set_Template_Letter_Sender_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Set_Template_Letter_Sender_0_document_0_desktop.png new file mode 100644 index 000000000..51af137ab Binary files /dev/null and b/backstop_data/bitmaps_reference/backstop_test_Set_Template_Letter_Sender_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Set_Template_Sender_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Set_Template_Sender_0_document_0_desktop.png new file mode 100644 index 000000000..db45f0738 Binary files /dev/null and b/backstop_data/bitmaps_reference/backstop_test_Set_Template_Sender_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Text_Not_Received_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Text_Not_Received_0_document_0_desktop.png index a1207863f..aaa97b31c 100644 Binary files a/backstop_data/bitmaps_reference/backstop_test_Text_Not_Received_0_document_0_desktop.png and b/backstop_data/bitmaps_reference/backstop_test_Text_Not_Received_0_document_0_desktop.png differ diff --git a/backstop_data/bitmaps_reference/backstop_test_Uploads_0_document_0_desktop.png b/backstop_data/bitmaps_reference/backstop_test_Uploads_0_document_0_desktop.png new file mode 100644 index 000000000..004784ad6 Binary files /dev/null and b/backstop_data/bitmaps_reference/backstop_test_Uploads_0_document_0_desktop.png differ diff --git a/gunicorn_config.py b/gunicorn_config.py index 2643f78c9..65494fd14 100644 --- a/gunicorn_config.py +++ b/gunicorn_config.py @@ -2,8 +2,6 @@ import multiprocessing import os import sys import traceback -import logging -from app.utils import hilite import gunicorn # Let gunicorn figure out the right number of workers @@ -13,8 +11,6 @@ import gunicorn workers = multiprocessing.cpu_count() worker_class = "gevent" -logging.basicConfig(level=logging.INFO) -logging.info(hilite("Gunicorn timeout set to 240 seconds")) timeout = 240 bind = "0.0.0.0:{}".format(os.getenv("PORT")) disable_redirect_access_to_syslog = True diff --git a/tests/app/main/views/test_api_integration.py b/tests/app/main/views/test_api_integration.py index ecfb9a196..1bb371ab3 100644 --- a/tests/app/main/views/test_api_integration.py +++ b/tests/app/main/views/test_api_integration.py @@ -182,10 +182,14 @@ def test_should_show_create_api_key_page( for index, option in enumerate(expected_options): item = page.select(".usa-radio")[index] if type(option) is tuple: - assert normalize_spaces(item.select_one(".usa-label").text) == option[0] - assert normalize_spaces(item.select_one(".usa-hint").text) == option[1] + label = item.select_one(".usa-radio__label") + hint = label.select_one(".usa-hint") + # Get the label text without the hint text + label_text = label.text.replace(hint.text, "").strip() if hint else label.text + assert normalize_spaces(label_text) == option[0] + assert normalize_spaces(hint.text) == option[1] else: - assert normalize_spaces(item.select_one(".usa-label").text) == option + assert normalize_spaces(item.select_one(".usa-radio__label").text) == option def test_should_create_api_key_with_type_normal( diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py index 37e0ccbf4..406853bbc 100644 --- a/tests/app/main/views/test_send.py +++ b/tests/app/main/views/test_send.py @@ -144,11 +144,11 @@ def test_default_email_sender_is_checked_and_has_hint( ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - assert page.select(".usa-radios input")[0].has_attr("checked") + assert page.select(".usa-radio input")[0].has_attr("checked") assert ( - normalize_spaces(page.select_one(".usa-radios .usa-hint").text) == "(Default)" + normalize_spaces(page.select_one(".usa-radio .usa-hint").text) == "(Default)" ) - assert not page.select(".usa-radios input")[1].has_attr("checked") + assert not page.select(".usa-radio input")[1].has_attr("checked") def test_default_sms_sender_is_checked_and_has_hint( @@ -161,11 +161,11 @@ def test_default_sms_sender_is_checked_and_has_hint( ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - assert page.select(".usa-radios input")[0].has_attr("checked") + assert page.select(".usa-radio input")[0].has_attr("checked") assert ( - normalize_spaces(page.select_one(".usa-radios .usa-hint").text) == "(Default)" + normalize_spaces(page.select_one(".usa-radio .usa-hint").text) == "(Default)" ) - assert not page.select(".usa-radios input")[1].has_attr("checked") + assert not page.select(".usa-radio input")[1].has_attr("checked") def test_default_sms_sender_is_checked_and_has_hint_when_there_are_no_inbound_numbers( @@ -178,11 +178,11 @@ def test_default_sms_sender_is_checked_and_has_hint_when_there_are_no_inbound_nu ".set_sender", service_id=SERVICE_ONE_ID, template_id=fake_uuid ) - assert page.select(".usa-radios input")[0].has_attr("checked") + assert page.select(".usa-radio input")[0].has_attr("checked") assert ( - normalize_spaces(page.select_one(".usa-radios .usa-hint").text) == "(Default)" + normalize_spaces(page.select_one(".usa-radio .usa-hint").text) == "(Default)" ) - assert not page.select(".usa-radios input")[1].has_attr("checked") + assert not page.select(".usa-radio input")[1].has_attr("checked") def test_default_inbound_sender_is_checked_and_has_hint_with_default_and_receives_text( @@ -196,13 +196,13 @@ def test_default_inbound_sender_is_checked_and_has_hint_with_default_and_receive ".set_sender", service_id=service_one["id"], template_id=fake_uuid ) - assert page.select(".usa-radios input")[0].has_attr("checked") + assert page.select(".usa-radio input")[0].has_attr("checked") assert ( - normalize_spaces(page.select_one(".usa-radios .usa-hint").text) + normalize_spaces(page.select_one(".usa-radio .usa-hint").text) == "(Default and receives replies)" ) - assert not page.select(".usa-radios input")[1].has_attr("checked") - assert not page.select(".usa-radios input")[2].has_attr("checked") + assert not page.select(".usa-radio input")[1].has_attr("checked") + assert not page.select(".usa-radio input")[2].has_attr("checked") def test_sms_sender_has_receives_replies_hint( @@ -216,13 +216,13 @@ def test_sms_sender_has_receives_replies_hint( ".set_sender", service_id=service_one["id"], template_id=fake_uuid ) - assert page.select(".usa-radios input")[0].has_attr("checked") + assert page.select(".usa-radio input")[0].has_attr("checked") assert ( - normalize_spaces(page.select_one(".usa-radios .usa-hint").text) + normalize_spaces(page.select_one(".usa-radio .usa-hint").text) == "(Default and receives replies)" ) - assert not page.select(".usa-radios input")[1].has_attr("checked") - assert not page.select(".usa-radios input")[2].has_attr("checked") + assert not page.select(".usa-radio input")[1].has_attr("checked") + assert not page.select(".usa-radio input")[2].has_attr("checked") @pytest.mark.parametrize( diff --git a/tests/javascripts/activityChart.test.js b/tests/javascripts/activityChart.test.js index 3d0087ec1..89e924dc1 100644 --- a/tests/javascripts/activityChart.test.js +++ b/tests/javascripts/activityChart.test.js @@ -283,3 +283,58 @@ test('handleDropdownChange updates DOM for individual selection', () => { window.fetchData.mockRestore(); }); + +test('handleDropdownChange shows empty message when user has no jobs', () => { + document.body.innerHTML = ` +
+
+
+
+
+
+

+ + + + + + + + + + +
Other User
Another User
Different User
No batched job messages found (messages are kept for 7 days).
+
+ + `; + + window.currentUserName = "Test User"; + + jest.spyOn(window, 'fetchData').mockImplementation(() => {}); + + const selectElement = document.getElementById('options'); + selectElement.value = 'individual'; + const event = { target: selectElement }; + + window.handleDropdownChange(event); + + expect(document.getElementById('table-heading').textContent).toBe('My activity'); + expect(document.getElementById('caption').textContent).toContain('Test User'); + + document.querySelectorAll('.sender-column').forEach(col => { + expect(col.style.display).toBe('none'); + }); + + const emptyMessageRow = document.querySelector('.table-empty-message').closest('tr'); + expect(emptyMessageRow.style.display).toBe(''); + + const allRows = Array.from(document.querySelectorAll('#activity-table tbody tr')); + const visibleRows = allRows.filter(row => row.style.display !== 'none'); + expect(visibleRows.length).toBe(1); + expect(visibleRows[0].querySelector('.table-empty-message')).not.toBeNull(); + + window.fetchData.mockRestore(); +}); diff --git a/tests/javascripts/listEntry.test.js b/tests/javascripts/listEntry.test.js index 5a4d47543..4bcc50e03 100644 --- a/tests/javascripts/listEntry.test.js +++ b/tests/javascripts/listEntry.test.js @@ -48,10 +48,10 @@ describe("List entry", () => { result += `
-
`; } @@ -247,7 +247,7 @@ describe("List entry", () => { triggerEvent(inputList.querySelectorAll('.input-list__button--remove')[0], 'click'); const newNums = Array.from( - inputList.querySelectorAll('.govuk-input--numbered__label') + inputList.querySelectorAll('.usa-label') ) .map((itemNum, idx) => { return parseInt(itemNum.lastChild.nodeValue, 10); @@ -350,4 +350,72 @@ describe("List entry", () => { }); }); + + describe("getOriginalClasses functionality", () => { + test("Should handle inputs without any classes", () => { + // Create input without classes + document.body.innerHTML = ` +
+
+ +
+
`; + + inputList = document.querySelector('.input-list'); + + // start module + window.GOVUK.modules.start(); + + // Check that it handles missing classes gracefully + const newInput = inputList.querySelector('.list-entry input'); + expect(newInput).not.toBeNull(); + }); + + test("Should handle empty input list", () => { + // Create div without any inputs + document.body.innerHTML = ` +
+
`; + + inputList = document.querySelector('.input-list'); + + // This should not throw an error + expect(() => { + window.GOVUK.modules.start(); + }).not.toThrow(); + }); + }); + + describe("getId functionality", () => { + test("Should generate IDs correctly with and without number parameter", () => { + // Set up a simpler structure to test ID generation + document.body.innerHTML = ` +
+
+ +
+
+ +
+
`; + + inputList = document.querySelector('.input-list'); + + // start module + window.GOVUK.modules.start(); + + // After module starts, check that IDs are generated correctly + // The module will have regenerated the DOM + const inputs = inputList.querySelectorAll('input'); + + // Check that inputs have proper IDs with numbers + expect(inputs[0].id).toEqual("test-1"); + expect(inputs[1].id).toEqual("test-2"); + + // The getId function is called internally during render + // We can verify it worked by checking the generated HTML structure + expect(inputList.innerHTML).toContain('id="test-1"'); + expect(inputList.innerHTML).toContain('id="test-2"'); + }); + }); }); diff --git a/urls.js b/urls.js index 54053bdd7..c53398bb9 100644 --- a/urls.js +++ b/urls.js @@ -14,6 +14,10 @@ const routes = { { label: 'Email Not Received', path: '/email-not-received' }, { label: 'Text Not Received', path: '/text-not-received' }, { label: 'Performance', path: '/performance' }, + // Registration pages with radio buttons + // Note: /register returns 404, registration is handled through login.gov + // { label: 'Register', path: '/register' }, + // { label: 'Register From Invite', path: '/register/from-invite/example-token' }, ], authenticated: [ @@ -67,16 +71,50 @@ const routes = { label: 'Invite User', path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/users/invite', }, - // Platform admin pages with checkboxes { label: 'Platform Admin Live Services', path: '/platform-admin/live-services', }, - // Organization pages { label: 'Add Organization', path: '/organizations/add', }, + { + label: 'Uploads', + path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/uploads', + }, + { + label: 'API Guest List', + path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/api/guest-list', + }, + // User profile and auth pages with radio buttons + // Note: /set-up-your-profile requires special auth flow from login.gov + // { + // label: 'Set Up Your Profile', + // path: '/set-up-your-profile', + // }, + { + label: 'Platform Admin User Auth Type', + path: '/users/6af522d0-2915-4e52-83a3-3690455a5fe6/change_auth', + }, + // Service settings pages with radio buttons + { + label: 'Data Retention Add', + path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/data-retention/add', + }, + { + label: 'Link Service to Organization', + path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/service-settings/link-service-to-organization', + }, + // Template sender pages with radio buttons + { + label: 'Set Template Sender', + path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/send/9c2b7a55-8785-4dc6-84d6-eb0e0615590d/set-sender', + }, + { + label: 'Set Template Letter Sender', + path: '/services/829ac564-59e9-47c5-ad69-e91315641c31/templates/9c2b7a55-8785-4dc6-84d6-eb0e0615590d/set-template-sender', + }, ], // Using Notify section