From 507c33a4db03413739d0cc91c809dbac2ffbeaf0 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Thu, 16 Oct 2025 17:42:42 -0700 Subject: [PATCH 01/10] Add services overview table to organization dashboard --- app/main/views/organizations.py | 1 + .../organizations/organization/index.html | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 01cdf498f..dc41474d0 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -115,6 +115,7 @@ def organization_dashboard(org_id): return render_template( "views/organizations/organization/index.html", selected_year=year, + services=current_organization.services, **message_allowance, **service_counts, ) diff --git a/app/templates/views/organizations/organization/index.html b/app/templates/views/organizations/organization/index.html index 88a2d2433..080e12d0b 100644 --- a/app/templates/views/organizations/organization/index.html +++ b/app/templates/views/organizations/organization/index.html @@ -73,4 +73,47 @@ + +
+

Services Overview

+ + + + + + + + + + + + + {% if services %} + {% for service in services %} + + + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
NameStatusUsagePrimary ContactRecent Template UsedCreated
{{ service.name }} + {% if not service.active %} + Suspended + {% elif service.restricted %} + Trial + {% else %} + Live + {% endif %} + {{ service.usage|default('N/A') }}{{ service.primary_contact|default('N/A') }}{{ service.recent_template|default('N/A') }}{{ service.created_at.strftime('%b %d, %Y') if service.created_at else 'N/A' }}
No services found within this organization
+
+ {% endblock %} From 965f3780fd6897d1478f507a89aed2baa28298a6 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Fri, 17 Oct 2025 15:54:45 -0700 Subject: [PATCH 02/10] toggle description --- .../organizations/organization/index.html | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/app/templates/views/organizations/organization/index.html b/app/templates/views/organizations/organization/index.html index 080e12d0b..bb6e89983 100644 --- a/app/templates/views/organizations/organization/index.html +++ b/app/templates/views/organizations/organization/index.html @@ -58,20 +58,23 @@ -
-

What is a service?

-

- When you join Notify, you're added to a service. This is your organization's workspace for sending text messages and emails. Within your service, you can: -

-
    -
  1. Create and edit message templates
  2. -
  3. Send messages to recipients
  4. -
  5. View message status and history
  6. -
  7. Manage team members and permissions
  8. -
  9. Track usage and delivery statistics
  10. -
  11. If you work for multiple organizations, you may belong to multiple services and can switch between them.
  12. -
-
+
+ What is a service? +
+

+ When you join Notify, you're added to a service. This is your organization's workspace for sending text messages and emails. Within your service, you can: +

+
    +
  1. Create and edit message templates
  2. +
  3. Send messages to recipients
  4. +
  5. View message status and history
  6. +
  7. Manage team members and permissions
  8. +
  9. Track usage and delivery statistics
  10. +
  11. If you work for multiple organizations, you may belong to multiple services and can switch between them.
  12. +
+
+
+
From 0a3d21215cba1f003632ee04e1afeb19e0b6f23a Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 21 Oct 2025 12:26:56 -0700 Subject: [PATCH 03/10] get service and usage --- app/main/views/organizations.py | 45 ++++++++++++++++++- app/models/organization.py | 4 +- app/notify_client/organizations_api_client.py | 4 +- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 84532d6b3..74a8a63cc 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -25,6 +25,7 @@ from app.main.views.dashboard import ( from app.models.organization import AllOrganizations, Organization from app.models.user import InvitedOrgUser, User from app.utils.csv import Spreadsheet +from app.utils.time import parse_naive_dt from app.utils.user import user_has_permissions, user_is_platform_admin from notifications_python_client.errors import HTTPError @@ -76,6 +77,46 @@ def get_organization_message_allowance(org_id): } +def get_services_usage(organization, year): + + try: + services_and_usage = organization.services_and_usage(financial_year=year, include_all_services=True)["services"] + except Exception as e: + current_app.logger.error(f"Error fetching services and usage: {e}") + return [] + + services = [] + for service in services_and_usage: + service["id"] = service.get("service_id") + service["name"] = service.get("service_name") + + emails_sent = service.get("emails_sent", 0) + sms_sent = service.get("sms_billable_units", 0) + sms_remainder = service.get("sms_remainder", 0) + sms_cost = service.get("sms_cost", 0) + + usage_parts = [] + if emails_sent > 0: + usage_parts.append(f"{emails_sent:,} emails") + if sms_sent > 0 or sms_remainder > 0: + if sms_cost > 0: + usage_parts.append(f"{sms_sent:,} sms ({sms_remainder:,} remaining, ${sms_cost:,.2f})") + else: + usage_parts.append(f"{sms_sent:,} sms ({sms_remainder:,} remaining)") + + service["usage"] = ", ".join(usage_parts) if usage_parts else "No usage" + + if "created_at" in service and isinstance(service["created_at"], str): + try: + service["created_at"] = parse_naive_dt(service["created_at"]) + except (ValueError, TypeError): + service["created_at"] = None + + services.append(service) + + return services + + @main.route("/organizations/", methods=["GET"]) @user_has_permissions() def organization_dashboard(org_id): @@ -86,10 +127,12 @@ def organization_dashboard(org_id): message_allowance = get_organization_message_allowance(org_id) + services_with_usage = get_services_usage(current_organization, year) + return render_template( "views/organizations/organization/index.html", selected_year=year, - services=current_organization.services, + services=services_with_usage, live_services=len(current_organization.live_services), trial_services=len(current_organization.trial_services), suspended_services=len(current_organization.suspended_services), diff --git a/app/models/organization.py b/app/models/organization.py index 33f5f3ec4..e7eb09f37 100644 --- a/app/models/organization.py +++ b/app/models/organization.py @@ -139,8 +139,8 @@ class Organization(JSONModel, SortByNameMixin): def associate_service(self, service_id): organizations_client.update_service_organization(service_id, self.id) - def services_and_usage(self, financial_year): - return organizations_client.get_services_and_usage(self.id, financial_year) + def services_and_usage(self, financial_year, include_all_services=False): + return organizations_client.get_services_and_usage(self.id, financial_year, include_all_services) class Organizations(SerialisedModelCollection): diff --git a/app/notify_client/organizations_api_client.py b/app/notify_client/organizations_api_client.py index e7cca58ef..105f6dce6 100644 --- a/app/notify_client/organizations_api_client.py +++ b/app/notify_client/organizations_api_client.py @@ -72,10 +72,10 @@ class OrganizationsClient(NotifyAdminAPIClient): def remove_user_from_organization(self, org_id, user_id): return self.delete(f"/organizations/{org_id}/users/{user_id}") - def get_services_and_usage(self, org_id, year): + def get_services_and_usage(self, org_id, year, include_all_services=False): return self.get( url=f"/organizations/{org_id}/services-with-usage", - params={"year": str(year)}, + params={"year": str(year), "include_all_services": str(include_all_services).lower()}, ) def get_organization_message_usage(self, org_id): From b76ab1a679b33080c0d261a198fbe5c344ac50d0 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 21 Oct 2025 14:37:27 -0700 Subject: [PATCH 04/10] update date format --- app/main/views/organizations.py | 7 ------- app/templates/views/organizations/organization/index.html | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 74a8a63cc..3baba4f52 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -25,7 +25,6 @@ from app.main.views.dashboard import ( from app.models.organization import AllOrganizations, Organization from app.models.user import InvitedOrgUser, User from app.utils.csv import Spreadsheet -from app.utils.time import parse_naive_dt from app.utils.user import user_has_permissions, user_is_platform_admin from notifications_python_client.errors import HTTPError @@ -106,12 +105,6 @@ def get_services_usage(organization, year): service["usage"] = ", ".join(usage_parts) if usage_parts else "No usage" - if "created_at" in service and isinstance(service["created_at"], str): - try: - service["created_at"] = parse_naive_dt(service["created_at"]) - except (ValueError, TypeError): - service["created_at"] = None - services.append(service) return services diff --git a/app/templates/views/organizations/organization/index.html b/app/templates/views/organizations/organization/index.html index bb6e89983..7b9c8f188 100644 --- a/app/templates/views/organizations/organization/index.html +++ b/app/templates/views/organizations/organization/index.html @@ -107,7 +107,7 @@ {{ service.usage|default('N/A') }} {{ service.primary_contact|default('N/A') }} {{ service.recent_template|default('N/A') }} - {{ service.created_at.strftime('%b %d, %Y') if service.created_at else 'N/A' }} + {{ service.created_at|format_date_normal if service.created_at else 'N/A' }} {% endfor %} {% else %} From 0594a0bc548a8a6b1d0fff7948525c5cdcc53c02 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 21 Oct 2025 14:55:21 -0700 Subject: [PATCH 05/10] provide pytest --- .../views/organizations/test_organizations.py | 127 +++++++++++++++++- 1 file changed, 125 insertions(+), 2 deletions(-) diff --git a/tests/app/main/views/organizations/test_organizations.py b/tests/app/main/views/organizations/test_organizations.py index 3dc7e74d8..60307b5db 100644 --- a/tests/app/main/views/organizations/test_organizations.py +++ b/tests/app/main/views/organizations/test_organizations.py @@ -354,7 +354,7 @@ def test_organization_services_shows_live_services_and_usage( client_request.login(active_user_with_permissions) page = client_request.get(".organization_usage", org_id=ORGANISATION_ID) - mock.assert_called_once_with(ORGANISATION_ID, 2020) + mock.assert_called_once_with(ORGANISATION_ID, 2020, False) services = page.select("main h3") usage_rows = page.select("main .grid-col-6") @@ -447,7 +447,7 @@ def test_organization_services_filters_by_financial_year( org_id=ORGANISATION_ID, year=financial_year, ) - mock.assert_called_once_with(ORGANISATION_ID, financial_year) + mock.assert_called_once_with(ORGANISATION_ID, financial_year, False) assert normalize_spaces(page.select_one(".pill").text) == ( "2020 to 2021 fiscal year " "2019 to 2020 fiscal year " @@ -1538,6 +1538,11 @@ def test_organization_dashboard_shows_message_usage( "app.organizations_client.get_organization_services", return_value=[], ) + mocker.patch( + "app.organizations_client.get_services_and_usage", + return_value={"services": []}, + ) + client_request.login(active_user_with_permissions) page = client_request.get( ".organization_dashboard", @@ -1576,6 +1581,46 @@ def test_organization_dashboard_shows_service_counts( service_json(id_="3", name="Suspended", restricted=False, active=False), ], ) + mocker.patch( + "app.organizations_client.get_services_and_usage", + return_value={ + "services": [ + { + "service_id": "1", + "service_name": "Live Service", + "emails_sent": 1000, + "sms_billable_units": 300, + "sms_remainder": 249700, + "sms_cost": 25.50, + "active": True, + "restricted": False, + "created_at": "2023-01-15 10:30:00", + }, + { + "service_id": "2", + "service_name": "Trial Service", + "emails_sent": 500, + "sms_billable_units": 50, + "sms_remainder": 249950, + "sms_cost": 0, + "active": True, + "restricted": True, + "created_at": "2023-02-20 14:00:00", + }, + { + "service_id": "3", + "service_name": "Suspended", + "emails_sent": 0, + "sms_billable_units": 0, + "sms_remainder": 250000, + "sms_cost": 0, + "active": False, + "restricted": False, + "created_at": "2023-03-10 09:15:00", + }, + ] + }, + ) client_request.login(active_user_with_permissions) page = client_request.get( @@ -1592,3 +1637,81 @@ def test_organization_dashboard_shows_service_counts( assert "1 Live" in normalize_spaces(service_box.text) assert "1 Trial" in normalize_spaces(service_box.text) assert "1 Suspended" in normalize_spaces(service_box.text) + + +def test_organization_dashboard_services_table_shows_usage( + client_request, + mock_get_organization, + mocker, + active_user_with_permissions, +): + mocker.patch( + "app.organizations_client.get_organization_message_usage", + return_value={ + "messages_sent": 0, + "messages_remaining": 0, + "total_message_limit": 0, + }, + ) + mocker.patch( + "app.organizations_client.get_organization_services", + return_value=[ + service_json(id_="1", name="Service One", restricted=False, active=True), + service_json(id_="2", name="Service Two", restricted=True, active=True), + ], + ) + mocker.patch( + "app.organizations_client.get_services_and_usage", + return_value={ + "services": [ + { + "service_id": "1", + "service_name": "Service One", + "emails_sent": 1500, + "sms_billable_units": 500, + "sms_remainder": 249500, + "sms_cost": 42.75, + "active": True, + "restricted": False, + "created_at": "2023-01-15 10:30:00", + }, + { + "service_id": "2", + "service_name": "Service Two", + "emails_sent": 250, + "sms_billable_units": 100, + "sms_remainder": 249900, + "sms_cost": 0, + "active": True, + "restricted": True, + "created_at": "2023-02-20 14:00:00", + }, + ] + }, + ) + + client_request.login(active_user_with_permissions) + page = client_request.get( + ".organization_dashboard", + org_id=ORGANISATION_ID, + ) + + table = page.select_one("table.usa-table") + assert table is not None + + rows = table.select("tbody tr") + assert len(rows) == 2 + + first_row_cells = rows[0].select("td") + assert normalize_spaces(first_row_cells[0].text) == "Service One" + assert normalize_spaces(first_row_cells[1].text) == "Live" + assert "1,500 emails" in normalize_spaces(first_row_cells[2].text) + assert "500 sms" in normalize_spaces(first_row_cells[2].text) + assert "249,500 remaining" in normalize_spaces(first_row_cells[2].text) + + second_row_cells = rows[1].select("td") + assert normalize_spaces(second_row_cells[0].text) == "Service Two" + assert normalize_spaces(second_row_cells[1].text) == "Trial" + assert "250 emails" in normalize_spaces(second_row_cells[2].text) + assert "100 sms" in normalize_spaces(second_row_cells[2].text) + assert "249,900 remaining" in normalize_spaces(second_row_cells[2].text) From 11a5f01fa4bcc9ec1933395b37bb036fc91d4da2 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 21 Oct 2025 15:31:37 -0700 Subject: [PATCH 06/10] update table columns --- app/templates/views/organizations/organization/index.html | 4 +--- tests/app/main/views/organizations/test_organizations.py | 5 ----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/templates/views/organizations/organization/index.html b/app/templates/views/organizations/organization/index.html index 7b9c8f188..e0485d413 100644 --- a/app/templates/views/organizations/organization/index.html +++ b/app/templates/views/organizations/organization/index.html @@ -87,7 +87,6 @@ Usage Primary Contact Recent Template Used - Created @@ -107,12 +106,11 @@ {{ service.usage|default('N/A') }} {{ service.primary_contact|default('N/A') }} {{ service.recent_template|default('N/A') }} - {{ service.created_at|format_date_normal if service.created_at else 'N/A' }} {% endfor %} {% else %} - No services found within this organization + No services found within this organization {% endif %} diff --git a/tests/app/main/views/organizations/test_organizations.py b/tests/app/main/views/organizations/test_organizations.py index 60307b5db..674f18da8 100644 --- a/tests/app/main/views/organizations/test_organizations.py +++ b/tests/app/main/views/organizations/test_organizations.py @@ -1594,7 +1594,6 @@ def test_organization_dashboard_shows_service_counts( "sms_cost": 25.50, "active": True, "restricted": False, - "created_at": "2023-01-15 10:30:00", }, { "service_id": "2", @@ -1605,7 +1604,6 @@ def test_organization_dashboard_shows_service_counts( "sms_cost": 0, "active": True, "restricted": True, - "created_at": "2023-02-20 14:00:00", }, { "service_id": "3", @@ -1616,7 +1614,6 @@ def test_organization_dashboard_shows_service_counts( "sms_cost": 0, "active": False, "restricted": False, - "created_at": "2023-03-10 09:15:00", }, ] }, @@ -1673,7 +1670,6 @@ def test_organization_dashboard_services_table_shows_usage( "sms_cost": 42.75, "active": True, "restricted": False, - "created_at": "2023-01-15 10:30:00", }, { "service_id": "2", @@ -1684,7 +1680,6 @@ def test_organization_dashboard_services_table_shows_usage( "sms_cost": 0, "active": True, "restricted": True, - "created_at": "2023-02-20 14:00:00", }, ] }, From 362c52ce8b5e040e7162e6af8b42e0c3e252d296 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 21 Oct 2025 15:37:43 -0700 Subject: [PATCH 07/10] table-overflow-x-auto --- .../organizations/organization/index.html | 70 ++++++++++--------- 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/app/templates/views/organizations/organization/index.html b/app/templates/views/organizations/organization/index.html index e0485d413..600405b7b 100644 --- a/app/templates/views/organizations/organization/index.html +++ b/app/templates/views/organizations/organization/index.html @@ -79,42 +79,44 @@

Services Overview

- - - - - - - - - - - - {% if services %} - {% for service in services %} +
+
NameStatusUsagePrimary ContactRecent Template Used
+ - - - - - + + + + + - {% endfor %} - {% else %} - - - - {% endif %} - -
{{ service.name }} - {% if not service.active %} - Suspended - {% elif service.restricted %} - Trial - {% else %} - Live - {% endif %} - {{ service.usage|default('N/A') }}{{ service.primary_contact|default('N/A') }}{{ service.recent_template|default('N/A') }}NameStatusUsagePrimary ContactRecent Template Used
No services found within this organization
+ + + {% if services %} + {% for service in services %} + + {{ service.name }} + + {% if not service.active %} + Suspended + {% elif service.restricted %} + Trial + {% else %} + Live + {% endif %} + + {{ service.usage|default('N/A') }} + {{ service.primary_contact|default('N/A') }} + {{ service.recent_template|default('N/A') }} + + {% endfor %} + {% else %} + + No services found within this organization + + {% endif %} + + +
{% endblock %} From 9ead834492ca04809d224a7825a2845909b3e561 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Fri, 24 Oct 2025 14:40:17 -0700 Subject: [PATCH 08/10] adding templates and refactor to use dashboard data endpoint --- app/main/views/organizations.py | 12 +++++------- app/notify_client/organizations_api_client.py | 6 ++++++ .../main/views/organizations/test_organizations.py | 6 ++++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 3baba4f52..3cbbb0b37 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -77,17 +77,17 @@ def get_organization_message_allowance(org_id): def get_services_usage(organization, year): - try: - services_and_usage = organization.services_and_usage(financial_year=year, include_all_services=True)["services"] + dashboard_data = organizations_client.get_organization_dashboard(organization.id, year) + services = dashboard_data.get("services", []) except Exception as e: - current_app.logger.error(f"Error fetching services and usage: {e}") + current_app.logger.error(f"Error fetching dashboard data: {e}") return [] - services = [] - for service in services_and_usage: + for service in services: service["id"] = service.get("service_id") service["name"] = service.get("service_name") + service["recent_template"] = service.get("recent_sms_template_name") emails_sent = service.get("emails_sent", 0) sms_sent = service.get("sms_billable_units", 0) @@ -105,8 +105,6 @@ def get_services_usage(organization, year): service["usage"] = ", ".join(usage_parts) if usage_parts else "No usage" - services.append(service) - return services diff --git a/app/notify_client/organizations_api_client.py b/app/notify_client/organizations_api_client.py index 105f6dce6..08b848fc2 100644 --- a/app/notify_client/organizations_api_client.py +++ b/app/notify_client/organizations_api_client.py @@ -83,5 +83,11 @@ class OrganizationsClient(NotifyAdminAPIClient): url="/organizations/{}/message-allowance".format(org_id), ) + def get_organization_dashboard(self, org_id, year): + return self.get( + url=f"/organizations/{org_id}/dashboard", + params={"year": str(year)}, + ) + organizations_client = OrganizationsClient() diff --git a/tests/app/main/views/organizations/test_organizations.py b/tests/app/main/views/organizations/test_organizations.py index 674f18da8..676f83dc5 100644 --- a/tests/app/main/views/organizations/test_organizations.py +++ b/tests/app/main/views/organizations/test_organizations.py @@ -1582,7 +1582,7 @@ def test_organization_dashboard_shows_service_counts( ], ) mocker.patch( - "app.organizations_client.get_services_and_usage", + "app.organizations_client.get_organization_dashboard", return_value={ "services": [ { @@ -1658,7 +1658,7 @@ def test_organization_dashboard_services_table_shows_usage( ], ) mocker.patch( - "app.organizations_client.get_services_and_usage", + "app.organizations_client.get_organization_dashboard", return_value={ "services": [ { @@ -1670,6 +1670,7 @@ def test_organization_dashboard_services_table_shows_usage( "sms_cost": 42.75, "active": True, "restricted": False, + "recent_sms_template_name": "Welcome SMS", }, { "service_id": "2", @@ -1680,6 +1681,7 @@ def test_organization_dashboard_services_table_shows_usage( "sms_cost": 0, "active": True, "restricted": True, + "recent_sms_template_name": "Reminder SMS", }, ] }, From c7c4b37a2ade1767cc1eff8b55a9979d77fc1efd Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Mon, 27 Oct 2025 16:54:49 -0700 Subject: [PATCH 09/10] update test --- .../views/organizations/test_organizations.py | 55 +++++-------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/tests/app/main/views/organizations/test_organizations.py b/tests/app/main/views/organizations/test_organizations.py index 676f83dc5..ecb4be378 100644 --- a/tests/app/main/views/organizations/test_organizations.py +++ b/tests/app/main/views/organizations/test_organizations.py @@ -1539,7 +1539,7 @@ def test_organization_dashboard_shows_message_usage( return_value=[], ) mocker.patch( - "app.organizations_client.get_services_and_usage", + "app.organizations_client.get_organization_dashboard", return_value={"services": []}, ) @@ -1585,36 +1585,9 @@ def test_organization_dashboard_shows_service_counts( "app.organizations_client.get_organization_dashboard", return_value={ "services": [ - { - "service_id": "1", - "service_name": "Live Service", - "emails_sent": 1000, - "sms_billable_units": 300, - "sms_remainder": 249700, - "sms_cost": 25.50, - "active": True, - "restricted": False, - }, - { - "service_id": "2", - "service_name": "Trial Service", - "emails_sent": 500, - "sms_billable_units": 50, - "sms_remainder": 249950, - "sms_cost": 0, - "active": True, - "restricted": True, - }, - { - "service_id": "3", - "service_name": "Suspended", - "emails_sent": 0, - "sms_billable_units": 0, - "sms_remainder": 250000, - "sms_cost": 0, - "active": False, - "restricted": False, - }, + {"service_id": "1", "service_name": "Live Service", "active": True, "restricted": False}, + {"service_id": "2", "service_name": "Trial Service", "active": True, "restricted": True}, + {"service_id": "3", "service_name": "Suspended", "active": False, "restricted": False}, ] }, ) @@ -1642,13 +1615,11 @@ def test_organization_dashboard_services_table_shows_usage( mocker, active_user_with_permissions, ): + mocker.patch.dict("flask.current_app.config", {"ORGANIZATION_DASHBOARD_ENABLED": True}) + mocker.patch( "app.organizations_client.get_organization_message_usage", - return_value={ - "messages_sent": 0, - "messages_remaining": 0, - "total_message_limit": 0, - }, + return_value={"messages_sent": 0, "messages_remaining": 0, "total_message_limit": 0}, ) mocker.patch( "app.organizations_client.get_organization_services", @@ -1664,23 +1635,23 @@ def test_organization_dashboard_services_table_shows_usage( { "service_id": "1", "service_name": "Service One", + "active": True, + "restricted": False, "emails_sent": 1500, "sms_billable_units": 500, "sms_remainder": 249500, "sms_cost": 42.75, - "active": True, - "restricted": False, "recent_sms_template_name": "Welcome SMS", }, { "service_id": "2", "service_name": "Service Two", + "active": True, + "restricted": True, "emails_sent": 250, "sms_billable_units": 100, "sms_remainder": 249900, "sms_cost": 0, - "active": True, - "restricted": True, "recent_sms_template_name": "Reminder SMS", }, ] @@ -1705,6 +1676,8 @@ def test_organization_dashboard_services_table_shows_usage( assert "1,500 emails" in normalize_spaces(first_row_cells[2].text) assert "500 sms" in normalize_spaces(first_row_cells[2].text) assert "249,500 remaining" in normalize_spaces(first_row_cells[2].text) + assert normalize_spaces(first_row_cells[3].text) == "N/A" + assert normalize_spaces(first_row_cells[4].text) == "Welcome SMS" second_row_cells = rows[1].select("td") assert normalize_spaces(second_row_cells[0].text) == "Service Two" @@ -1712,3 +1685,5 @@ def test_organization_dashboard_services_table_shows_usage( assert "250 emails" in normalize_spaces(second_row_cells[2].text) assert "100 sms" in normalize_spaces(second_row_cells[2].text) assert "249,900 remaining" in normalize_spaces(second_row_cells[2].text) + assert normalize_spaces(second_row_cells[3].text) == "N/A" + assert normalize_spaces(second_row_cells[4].text) == "Reminder SMS" From 304efabeec974da3de546250baf17031ba2f7264 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Mon, 27 Oct 2025 17:28:31 -0700 Subject: [PATCH 10/10] update test --- app/main/views/organizations.py | 7 ++++--- app/models/organization.py | 4 ++-- app/notify_client/organizations_api_client.py | 4 ++-- app/templates/views/organizations/organization/index.html | 6 +++--- tests/app/main/views/organizations/test_organizations.py | 8 +++++--- 5 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 3cbbb0b37..51c8cb3fb 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -76,7 +76,7 @@ def get_organization_message_allowance(org_id): } -def get_services_usage(organization, year): +def get_services_dashboard_data(organization, year): try: dashboard_data = organizations_client.get_organization_dashboard(organization.id, year) services = dashboard_data.get("services", []) @@ -87,7 +87,8 @@ def get_services_usage(organization, year): for service in services: service["id"] = service.get("service_id") service["name"] = service.get("service_name") - service["recent_template"] = service.get("recent_sms_template_name") + service["recent_template"] = service.get("recent_sms_template_name") or "N/A" + service["primary_contact"] = service.get("primary_contact") or "N/A" emails_sent = service.get("emails_sent", 0) sms_sent = service.get("sms_billable_units", 0) @@ -118,7 +119,7 @@ def organization_dashboard(org_id): message_allowance = get_organization_message_allowance(org_id) - services_with_usage = get_services_usage(current_organization, year) + services_with_usage = get_services_dashboard_data(current_organization, year) return render_template( "views/organizations/organization/index.html", diff --git a/app/models/organization.py b/app/models/organization.py index e7eb09f37..33f5f3ec4 100644 --- a/app/models/organization.py +++ b/app/models/organization.py @@ -139,8 +139,8 @@ class Organization(JSONModel, SortByNameMixin): def associate_service(self, service_id): organizations_client.update_service_organization(service_id, self.id) - def services_and_usage(self, financial_year, include_all_services=False): - return organizations_client.get_services_and_usage(self.id, financial_year, include_all_services) + def services_and_usage(self, financial_year): + return organizations_client.get_services_and_usage(self.id, financial_year) class Organizations(SerialisedModelCollection): diff --git a/app/notify_client/organizations_api_client.py b/app/notify_client/organizations_api_client.py index 08b848fc2..e68b23f35 100644 --- a/app/notify_client/organizations_api_client.py +++ b/app/notify_client/organizations_api_client.py @@ -72,10 +72,10 @@ class OrganizationsClient(NotifyAdminAPIClient): def remove_user_from_organization(self, org_id, user_id): return self.delete(f"/organizations/{org_id}/users/{user_id}") - def get_services_and_usage(self, org_id, year, include_all_services=False): + def get_services_and_usage(self, org_id, year): return self.get( url=f"/organizations/{org_id}/services-with-usage", - params={"year": str(year), "include_all_services": str(include_all_services).lower()}, + params={"year": str(year)}, ) def get_organization_message_usage(self, org_id): diff --git a/app/templates/views/organizations/organization/index.html b/app/templates/views/organizations/organization/index.html index 600405b7b..6afc33c4d 100644 --- a/app/templates/views/organizations/organization/index.html +++ b/app/templates/views/organizations/organization/index.html @@ -104,9 +104,9 @@ Live {% endif %} - {{ service.usage|default('N/A') }} - {{ service.primary_contact|default('N/A') }} - {{ service.recent_template|default('N/A') }} + {{ service.usage }} + {{ service.primary_contact }} + {{ service.recent_template }} {% endfor %} {% else %} diff --git a/tests/app/main/views/organizations/test_organizations.py b/tests/app/main/views/organizations/test_organizations.py index ecb4be378..70389b8d6 100644 --- a/tests/app/main/views/organizations/test_organizations.py +++ b/tests/app/main/views/organizations/test_organizations.py @@ -354,7 +354,7 @@ def test_organization_services_shows_live_services_and_usage( client_request.login(active_user_with_permissions) page = client_request.get(".organization_usage", org_id=ORGANISATION_ID) - mock.assert_called_once_with(ORGANISATION_ID, 2020, False) + mock.assert_called_once_with(ORGANISATION_ID, 2020) services = page.select("main h3") usage_rows = page.select("main .grid-col-6") @@ -447,7 +447,7 @@ def test_organization_services_filters_by_financial_year( org_id=ORGANISATION_ID, year=financial_year, ) - mock.assert_called_once_with(ORGANISATION_ID, financial_year, False) + mock.assert_called_once_with(ORGANISATION_ID, financial_year) assert normalize_spaces(page.select_one(".pill").text) == ( "2020 to 2021 fiscal year " "2019 to 2020 fiscal year " @@ -1609,7 +1609,7 @@ def test_organization_dashboard_shows_service_counts( assert "1 Suspended" in normalize_spaces(service_box.text) -def test_organization_dashboard_services_table_shows_usage( +def test_organization_dashboard_services_table( client_request, mock_get_organization, mocker, @@ -1642,6 +1642,7 @@ def test_organization_dashboard_services_table_shows_usage( "sms_remainder": 249500, "sms_cost": 42.75, "recent_sms_template_name": "Welcome SMS", + "primary_contact": None, }, { "service_id": "2", @@ -1653,6 +1654,7 @@ def test_organization_dashboard_services_table_shows_usage( "sms_remainder": 249900, "sms_cost": 0, "recent_sms_template_name": "Reminder SMS", + "primary_contact": None, }, ] },