From 3beba2beaf72fbfe501c9a22724de0bea01d2b12 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Wed, 5 Nov 2025 13:20:21 -0800 Subject: [PATCH] Add service editing to organization dashboard with confirmation modal - Add edit form for service name, primary contact, and status - Add confirmation modal before saving changes - Refactor dashboard actions into helper functions - Add validation for service name and email format --- app/main/views/organizations.py | 166 ++++++++++++++++++++++---------- 1 file changed, 117 insertions(+), 49 deletions(-) diff --git a/app/main/views/organizations.py b/app/main/views/organizations.py index 5a42f986b..4e493a55d 100644 --- a/app/main/views/organizations.py +++ b/app/main/views/organizations.py @@ -1,8 +1,10 @@ +import re from collections import OrderedDict from datetime import datetime from functools import partial from flask import ( + Response, current_app, flash, redirect, @@ -35,11 +37,15 @@ from app.main.views.dashboard import ( requested_and_current_financial_year, ) from app.models.organization import AllOrganizations, Organization +from app.models.service import Service from app.models.user import InvitedOrgUser, User +from app.notify_client import cache from app.utils.csv import Spreadsheet from app.utils.user import user_has_permissions, user_is_platform_admin from notifications_python_client.errors import HTTPError +EMAIL_REGEX = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" + @main.route("/organizations", methods=["GET"]) @user_is_platform_admin @@ -88,6 +94,96 @@ def get_organization_message_allowance(org_id): } +def _handle_create_service(org_id): + create_service_form = CreateServiceForm( + organization_type=current_user.default_organization_type + or OrganizationType.FEDERAL + ) + + if request.method == "POST" and create_service_form.validate_on_submit(): + service_name = create_service_form.name.data + service_id, error = _create_service( + service_name, + create_service_form.organization_type.data, + email_safe(service_name), + create_service_form, + ) + if not error: + current_organization.associate_service(service_id) + flash(f"Service '{service_name}' has been created", "default_with_tick") + session["new_service_id"] = service_id + return redirect(url_for(".organization_dashboard", org_id=org_id)) + else: + flash("Error creating service", "error") + + return create_service_form + + +def _handle_invite_user(org_id): + invite_user_form = InviteOrgUserForm( + inviter_email_address=current_user.email_address + ) + + if request.method == "POST" and invite_user_form.validate_on_submit(): + try: + invited_org_user = InvitedOrgUser.create( + current_user.id, org_id, invite_user_form.email_address.data + ) + flash( + f"Invite sent to {invited_org_user.email_address}", + "default_with_tick", + ) + return redirect(url_for(".organization_dashboard", org_id=org_id)) + except Exception: + flash("Error sending invitation", "error") + + return invite_user_form + + +def _handle_edit_service(org_id, service_id): + service = Service.from_id(service_id) + + if request.method == "POST": + service_name = request.form.get("service_name", "").strip() + primary_contact = request.form.get("primary_contact", "").strip() + new_status = request.form.get("status") + + if not service_name: + flash("Service name is required", "error") + elif primary_contact and not re.match(EMAIL_REGEX, primary_contact): + flash("Please enter a valid email address", "error") + else: + if service_name != service.name: + service.update(name=service_name) + + if primary_contact != (service.billing_contact_email_addresses or ""): + service.update(billing_contact_email_addresses=primary_contact) + + current_status = "trial" if service.trial_mode else "live" + if new_status != current_status: + service.update_status(live=(new_status == "live")) + cache.redis_client.delete("organizations") + + flash("Service updated successfully", "default_with_tick") + session["updated_service_id"] = str(service_id) + return redirect(url_for(".organization_dashboard", org_id=org_id)) + + return { + "id": service.id, + "name": ( + request.form.get("service_name", "").strip() + if request.method == "POST" + else service.name + ), + "primary_contact": ( + request.form.get("primary_contact", "").strip() + if request.method == "POST" + else (service.billing_contact_email_addresses or "") + ), + "status": "trial" if service.trial_mode else "live", + } + + def get_services_dashboard_data(organization, year): try: dashboard_data = organizations_client.get_organization_dashboard( @@ -133,73 +229,45 @@ def organization_dashboard(org_id): year = requested_and_current_financial_year(request)[0] action = request.args.get("action") + service_id = request.args.get("service_id") create_service_form = None invite_user_form = None + edit_service_data = None - if action == "create-service" or request.form.get("form_name") == "create_service": - create_service_form = CreateServiceForm( - organization_type=current_user.default_organization_type - or OrganizationType.FEDERAL - ) + if action == "create-service": + result = _handle_create_service(org_id) + if isinstance(result, Response): + return result + create_service_form = result - if request.method == "POST" and create_service_form.validate_on_submit(): - service_name = create_service_form.name.data - service_id, error = _create_service( - service_name, - create_service_form.organization_type.data, - email_safe(service_name), - create_service_form, - ) - if not error: - current_organization.associate_service(service_id) - current_app.logger.info( - f"Service {service_id} created and associated with org {org_id}" - ) - flash(f"Service '{service_name}' has been created", "default_with_tick") - session["new_service_id"] = service_id - return redirect(url_for(".organization_dashboard", org_id=org_id)) - else: - current_app.logger.error(f"Error creating service: {error}") - flash("Error creating service", "error") + elif action == "invite-user": + result = _handle_invite_user(org_id) + if isinstance(result, Response): + return result + invite_user_form = result - if action == "invite-user" or request.form.get("form_name") == "invite_user": - invite_user_form = InviteOrgUserForm( - inviter_email_address=current_user.email_address - ) - - if request.method == "POST" and invite_user_form.validate_on_submit(): - try: - invited_org_user = InvitedOrgUser.create( - current_user.id, org_id, invite_user_form.email_address.data - ) - flash( - f"Invite sent to {invited_org_user.email_address}", - "default_with_tick", - ) - return redirect(url_for(".organization_dashboard", org_id=org_id)) - except Exception as e: - current_app.logger.error(f"Error inviting user: {e}") - flash("Error sending invitation", "error") + elif action == "edit-service" and service_id: + result = _handle_edit_service(org_id, service_id) + if isinstance(result, Response): + return result + edit_service_data = result message_allowance = get_organization_message_allowance(org_id) - services_with_usage = get_services_dashboard_data(current_organization, year) - new_service_id = session.pop("new_service_id", None) - return render_template( "views/organizations/organization/index.html", selected_year=year, - services=services_with_usage, + services=get_services_dashboard_data(current_organization, year), live_services=len(current_organization.live_services), trial_services=len(current_organization.trial_services), suspended_services=len(current_organization.suspended_services), total_services=len(current_organization.services), create_service_form=create_service_form, invite_user_form=invite_user_form, - show_create_service=create_service_form is not None, - show_invite_user=invite_user_form is not None, - new_service_id=new_service_id, + edit_service_data=edit_service_data, + new_service_id=session.pop("new_service_id", None), + updated_service_id=session.pop("updated_service_id", None), **message_allowance, )