diff --git a/.ds.baseline b/.ds.baseline index 9c1dcfeb65..0c5680c972 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -161,7 +161,7 @@ "filename": "app/config.py", "hashed_secret": "577a4c667e4af8682ca431857214b3a920883efc", "is_verified": false, - "line_number": 118, + "line_number": 127, "is_secret": false } ], @@ -634,5 +634,5 @@ } ] }, - "generated_at": "2025-10-07T17:43:26Z" + "generated_at": "2025-10-14T19:59:45Z" } diff --git a/app/config.py b/app/config.py index 92aee16705..502616c1b1 100644 --- a/app/config.py +++ b/app/config.py @@ -78,6 +78,10 @@ class Config(object): # TODO: reassign this NOTIFY_SERVICE_ID = "d6aa2c68-a2d9-4437-ab19-3ae8eb202553" + ORGANIZATION_DASHBOARD_ENABLED = ( + getenv("ORGANIZATION_DASHBOARD_ENABLED", "False") == "True" + ) + NOTIFY_BILLING_DETAILS = json.loads(getenv("NOTIFY_BILLING_DETAILS") or "null") or { "account_number": "98765432", "sort_code": "01-23-45", @@ -109,6 +113,11 @@ class Development(Config): ASSET_PATH = "/static/" NOTIFY_LOG_LEVEL = "DEBUG" + # Feature Flags + ORGANIZATION_DASHBOARD_ENABLED = ( + getenv("ORGANIZATION_DASHBOARD_ENABLED", "True") == "True" + ) + # Buckets CSV_UPLOAD_BUCKET = _s3_credentials_from_env("CSV") LOGO_UPLOAD_BUCKET = _s3_credentials_from_env("LOGO") diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 3c1e447ffd..3b1569f657 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -2,7 +2,7 @@ from datetime import datetime from functools import partial -from flask import flash, redirect, render_template, request, url_for +from flask import current_app, flash, redirect, render_template, request, url_for from flask_login import current_user from app import current_organization, org_invite_api_client, organizations_client @@ -65,13 +65,29 @@ def add_organization(): @main.route("/organizations/", methods=["GET"]) @user_has_permissions() def organization_dashboard(org_id): + if not current_app.config.get("ORGANIZATION_DASHBOARD_ENABLED", False): + return redirect(url_for(".organization_usage", org_id=org_id)) + + year = requested_and_current_financial_year(request)[0] + + # TODO: total message allowance + return render_template( + "views/organizations/organization/index.html", + selected_year=year, + ) + + +@main.route("/organizations//usage", methods=["GET"]) +@user_has_permissions() +def organization_usage(org_id): year, current_financial_year = requested_and_current_financial_year(request) services = current_organization.services_and_usage(financial_year=year)["services"] + return render_template( - "views/organizations/organization/index.html", + "views/organizations/organization/usage.html", services=services, years=get_tuples_of_financial_years( - partial(url_for, ".organization_dashboard", org_id=current_organization.id), + partial(url_for, ".organization_usage", org_id=current_organization.id), start=current_financial_year - 2, end=current_financial_year, ), @@ -90,14 +106,10 @@ def organization_dashboard(org_id): @main.route("/organizations//download-usage-report.csv", methods=["GET"]) @user_has_permissions() def download_organization_usage_report(org_id): - selected_year_input = request.args.get("selected_year") - # Validate selected_year to prevent header injection - if ( - selected_year_input - and selected_year_input.isdigit() - and len(selected_year_input) == 4 - ): - selected_year = selected_year_input + # Validate and sanitize selected_year to prevent header injection + selected_year_input = request.args.get("selected_year", "") + if selected_year_input.isdigit() and len(selected_year_input) == 4: + selected_year = str(int(selected_year_input)) else: selected_year = str(datetime.now().year) services_usage = current_organization.services_and_usage( @@ -142,13 +154,8 @@ def download_organization_usage_report(org_id): { "Content-Type": "text/csv; charset=utf-8", "Content-Disposition": ( - "inline;" - 'filename="{} organization usage report for year {}' - ' - generated on {}.csv"'.format( - safe_org_name, - selected_year, - datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ"), - ) + f'inline;filename="{safe_org_name} organization usage report for year {selected_year}' + f' - generated on {datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ")}.csv"' ), }, ) diff --git a/app/navigation.py b/app/navigation.py index 798067f8a8..037d5e6e83 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -333,6 +333,9 @@ class OrgNavigation(Navigation): "dashboard": { "organization_dashboard", }, + "usage": { + "organization_usage", + }, "settings": { "edit_organization_billing_details", "edit_organization_domains", diff --git a/app/templates/components/org_nav.html b/app/templates/components/org_nav.html index c29b41be26..7b54f2ffb6 100644 --- a/app/templates/components/org_nav.html +++ b/app/templates/components/org_nav.html @@ -1,6 +1,9 @@