mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-07-15 19:01:04 -04:00
Merge pull request #3077 from GSA/2970-service-status-display-live-vs-trial
Add service editing to organization dashboard with confirmation modal
This commit is contained in:
@@ -60,6 +60,7 @@ import { initCurrentYear } from './date.js';
|
||||
import './sidenav.js';
|
||||
import './validation.js';
|
||||
import './scrollPosition.js';
|
||||
import './organizationDashboard.js';
|
||||
|
||||
initCurrentYear();
|
||||
|
||||
|
||||
68
app/assets/javascripts/organizationDashboard.js
Normal file
68
app/assets/javascripts/organizationDashboard.js
Normal file
@@ -0,0 +1,68 @@
|
||||
(function(window) {
|
||||
'use strict';
|
||||
|
||||
function scrollToElement(element, delay) {
|
||||
setTimeout(function() {
|
||||
element.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest'
|
||||
});
|
||||
}, delay || 0);
|
||||
}
|
||||
|
||||
function focusFirstInput(container, delay) {
|
||||
setTimeout(function() {
|
||||
var input = container.querySelector('input[type="text"], input[type="email"]');
|
||||
if (input) input.focus();
|
||||
}, delay || 0);
|
||||
}
|
||||
|
||||
function highlightAndScrollToService(serviceId) {
|
||||
var serviceRow = document.getElementById('service-' + serviceId);
|
||||
if (serviceRow) {
|
||||
scrollToElement(serviceRow, 300);
|
||||
|
||||
setTimeout(function() {
|
||||
serviceRow.classList.remove('is-highlighted');
|
||||
if (serviceRow.className === '') {
|
||||
serviceRow.removeAttribute('class');
|
||||
}
|
||||
}, 3300);
|
||||
}
|
||||
}
|
||||
|
||||
function initForms() {
|
||||
requestAnimationFrame(function() {
|
||||
var form = document.getElementById('create-service-form') ||
|
||||
document.getElementById('invite-user-form') ||
|
||||
document.getElementById('edit-service-form');
|
||||
|
||||
if (form) {
|
||||
scrollToElement(form, 50);
|
||||
focusFirstInput(form, 150);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initEditServiceConfirmation() {
|
||||
var confirmEditButton = document.getElementById('edit-service-confirm-btn');
|
||||
if (confirmEditButton) {
|
||||
confirmEditButton.addEventListener('click', function() {
|
||||
var editForm = document.getElementById('edit-service-form');
|
||||
if (editForm) {
|
||||
editForm.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initForms();
|
||||
initEditServiceConfirmation();
|
||||
});
|
||||
|
||||
window.OrganizationDashboard = {
|
||||
highlightAndScrollToService: highlightAndScrollToService
|
||||
};
|
||||
})(window);
|
||||
@@ -365,6 +365,14 @@ td.table-empty-message {
|
||||
}
|
||||
}
|
||||
|
||||
.usa-fieldset {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.usa-radio {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.spark-bar-bar {
|
||||
font-size: units(2);
|
||||
background-color: transparent;
|
||||
|
||||
@@ -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,
|
||||
@@ -12,6 +14,7 @@ from flask import (
|
||||
url_for,
|
||||
)
|
||||
from flask_login import current_user
|
||||
from markupsafe import escape
|
||||
|
||||
from app import current_organization, org_invite_api_client, organizations_client
|
||||
from app.enums import OrganizationType
|
||||
@@ -35,11 +38,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 = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
|
||||
|
||||
|
||||
@main.route("/organizations", methods=["GET"])
|
||||
@user_is_platform_admin
|
||||
@@ -88,6 +95,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 EMAIL_REGEX.match(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": (
|
||||
escape(request.form.get("service_name", "").strip())
|
||||
if request.method == "POST"
|
||||
else service.name
|
||||
),
|
||||
"primary_contact": (
|
||||
escape(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 +230,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,
|
||||
)
|
||||
|
||||
@@ -270,8 +339,6 @@ def download_organization_usage_report(org_id):
|
||||
]
|
||||
|
||||
# Sanitize organization name for filename to prevent header injection
|
||||
import re
|
||||
|
||||
safe_org_name = re.sub(r"[^\w\s-]", "", current_organization.name).strip()
|
||||
safe_org_name = re.sub(r"[-\s]+", "-", safe_org_name)
|
||||
|
||||
|
||||
@@ -59,22 +59,21 @@
|
||||
</div>
|
||||
|
||||
<div class="display-flex flex-gap-1 margin-bottom-3">
|
||||
<a {% if not show_create_service %}href="{{ url_for('.organization_dashboard', org_id=current_org.id, action='create-service') }}"{% endif %}
|
||||
class="usa-button {% if show_create_service %}usa-button--base button-not-clickable{% else %}usa-button--outline{% endif %}">
|
||||
<a {% if not create_service_form %}href="{{ url_for('.organization_dashboard', org_id=current_org.id, action='create-service') }}"{% endif %}
|
||||
class="usa-button {% if create_service_form %}usa-button--base button-not-clickable{% else %}usa-button--outline{% endif %}">
|
||||
Create new service
|
||||
</a>
|
||||
<a {% if not show_invite_user %}href="{{ url_for('.organization_dashboard', org_id=current_org.id, action='invite-user') }}"{% endif %}
|
||||
class="usa-button {% if show_invite_user %}usa-button--base button-not-clickable{% else %}usa-button--outline{% endif %}">
|
||||
<a {% if not invite_user_form %}href="{{ url_for('.organization_dashboard', org_id=current_org.id, action='invite-user') }}"{% endif %}
|
||||
class="usa-button {% if invite_user_form %}usa-button--base button-not-clickable{% else %}usa-button--outline{% endif %}">
|
||||
Add org admin
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if show_create_service and create_service_form %}
|
||||
{% if create_service_form %}
|
||||
<div id="create-service-form" class="bg-base-lightest padding-3 radius-md margin-bottom-3">
|
||||
<h3 class="margin-top-0">Create a new service</h3>
|
||||
<form method="post" action="{{ url_for('.organization_dashboard', org_id=current_org.id, action='create-service') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ create_service_form.csrf_token._value() }}"/>
|
||||
<input type="hidden" name="form_name" value="create_service"/>
|
||||
|
||||
{{ create_service_form.name(param_extensions={"hint": {"text": "You can change this later"}}) }}
|
||||
|
||||
@@ -86,13 +85,12 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if show_invite_user and invite_user_form %}
|
||||
{% if invite_user_form %}
|
||||
<div id="invite-user-form" class="bg-base-lightest padding-3 radius-md margin-bottom-3">
|
||||
<h3 class="margin-top-0">Invite a team member</h3>
|
||||
<p class="margin-top-0">{{ current_org.name }} team members can see usage and team members for each service, and invite other team members.</p>
|
||||
<form method="post" action="{{ url_for('.organization_dashboard', org_id=current_org.id, action='invite-user') }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ invite_user_form.csrf_token._value() }}"/>
|
||||
<input type="hidden" name="form_name" value="invite_user"/>
|
||||
|
||||
{{ invite_user_form.email_address(param_extensions={"classes": ""}, error_message_with_html=True) }}
|
||||
|
||||
@@ -104,6 +102,81 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if edit_service_data %}
|
||||
<div class="bg-base-lightest padding-3 radius-md margin-bottom-3">
|
||||
<h3 class="margin-top-0">Edit Service</h3>
|
||||
<form id="edit-service-form" method="post" action="{{ url_for('.organization_dashboard', org_id=current_org.id, action='edit-service', service_id=edit_service_data.id) }}">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="usa-form-group">
|
||||
<label class="usa-label" for="service_name">Service Name</label>
|
||||
<input class="usa-input" type="text" id="service_name" name="service_name"
|
||||
value="{{ edit_service_data.name }}" required>
|
||||
</div>
|
||||
|
||||
<div class="usa-form-group">
|
||||
<label class="usa-label" for="primary_contact">Primary Contact Email</label>
|
||||
<input class="usa-input" type="email" id="primary_contact" name="primary_contact"
|
||||
value="{{ edit_service_data.primary_contact|default('') }}" placeholder="email@example.com">
|
||||
</div>
|
||||
|
||||
<fieldset class="usa-fieldset margin-top-2">
|
||||
<legend class="usa-legend">Service Status</legend>
|
||||
<div class="usa-radio">
|
||||
<input class="usa-radio__input" id="statusTrial" type="radio" name="status" value="trial"
|
||||
{% if edit_service_data.status == 'trial' %}checked{% endif %}>
|
||||
<label class="usa-radio__label" for="statusTrial">Trial</label>
|
||||
</div>
|
||||
<div class="usa-radio">
|
||||
<input class="usa-radio__input" id="statusLive" type="radio" name="status" value="live"
|
||||
{% if edit_service_data.status == 'live' %}checked{% endif %}>
|
||||
<label class="usa-radio__label" for="statusLive">Live</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="display-flex flex-gap-1 margin-top-3">
|
||||
<button type="button" class="usa-button" data-open-modal="confirmEditModal">Save Changes</button>
|
||||
<a href="{{ url_for('.organization_dashboard', org_id=current_org.id) }}" class="usa-button usa-button--outline">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="usa-modal"
|
||||
data-module="usa-modal"
|
||||
id="confirmEditModal"
|
||||
aria-hidden="true"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="confirmEditModalHeading"
|
||||
aria-describedby="confirmEditModalDesc"
|
||||
>
|
||||
<div class="usa-modal__content">
|
||||
<div class="usa-modal__main">
|
||||
<h2 class="usa-modal__heading font-body-lg" id="confirmEditModalHeading">Are you sure you want to save these changes?</h2>
|
||||
<p id="confirmEditModalDesc">This will update the service name, primary contact, and/or status. If you're changing the status to Live, team members will be notified.</p>
|
||||
<div class="usa-modal__footer">
|
||||
<ul class="usa-button-group">
|
||||
<li class="usa-button-group__item">
|
||||
<button type="button" id="edit-service-confirm-btn" class="usa-button">Confirm changes</button>
|
||||
</li>
|
||||
<li class="usa-button-group__item">
|
||||
<button class="usa-button usa-button--unstyled padding-105 text-center" data-close-modal type="button">
|
||||
No, go back
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<button class="usa-button usa-modal__close" aria-label="Close this window" data-close-modal type="button">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||
<use xlink:href="{{ asset_url('img/sprite.svg') }}#close"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="usa-accordion margin-bottom-3">
|
||||
<h3 class="usa-accordion__heading">
|
||||
<button
|
||||
@@ -134,21 +207,23 @@
|
||||
<div class="margin-bottom-5">
|
||||
<h2 class="font-heading-lg margin-bottom-2">Services Overview</h2>
|
||||
<div class="table-overflow-x-auto">
|
||||
<table class="usa-table">
|
||||
<table class="usa-table service-dashboard-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" role="columnheader">Name</th>
|
||||
<th scope="col" role="columnheader">Status</th>
|
||||
<th scope="col" role="columnheader">Usage</th>
|
||||
<th scope="col" role="columnheader">Primary Contact</th>
|
||||
<th scope="col" role="columnheader">Recent Template Used</th>
|
||||
<th scope="col" role="columnheader">Last Used</th>
|
||||
<th scope="col" role="columnheader">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if services %}
|
||||
{% for service in services %}
|
||||
{% set is_new_service = new_service_id and service.id == new_service_id %}
|
||||
<tr id="service-{{ service.id }}" {% if is_new_service %}class="is-highlighted"{% endif %}>
|
||||
{% set is_updated_service = updated_service_id and service.id == updated_service_id %}
|
||||
<tr id="service-{{ service.id }}" {% if is_new_service or is_updated_service %}class="is-highlighted"{% endif %}>
|
||||
<td><a href="{{ url_for('main.service_dashboard', service_id=service.id) }}" class="usa-link">{{ service.name }}</a></td>
|
||||
<td>
|
||||
{% if not service.active %}
|
||||
@@ -162,11 +237,23 @@
|
||||
<td>{{ service.usage }}</td>
|
||||
<td>{{ service.primary_contact }}</td>
|
||||
<td>{{ service.recent_template }}</td>
|
||||
<td>
|
||||
{% if service.active %}
|
||||
{% set is_editing = edit_service_data and edit_service_data.id == service.id %}
|
||||
{% if is_editing %}
|
||||
<span class="text-base">Edit</span>
|
||||
{% else %}
|
||||
<a href="{{ url_for('.organization_dashboard', org_id=current_org.id, action='edit-service', service_id=service.id) }}" class="usa-link">
|
||||
Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="table-empty-message">No services found within this organization</td>
|
||||
<td colspan="6" class="table-empty-message">No services found within this organization</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
@@ -178,48 +265,19 @@
|
||||
|
||||
{% block extra_javascripts %}
|
||||
<script nonce="{{ csp_nonce() }}">
|
||||
(function() {
|
||||
function scrollToElement(element, delay) {
|
||||
setTimeout(function() {
|
||||
element.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest'
|
||||
});
|
||||
}, delay || 0);
|
||||
}
|
||||
|
||||
function focusFirstInput(container, delay) {
|
||||
setTimeout(function() {
|
||||
var input = container.querySelector('input[type="text"], input[type="search"]');
|
||||
if (input) input.focus();
|
||||
}, delay || 0);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
{% if new_service_id %}
|
||||
var serviceRow = document.getElementById('service-{{ new_service_id }}');
|
||||
if (serviceRow) {
|
||||
scrollToElement(serviceRow, 300);
|
||||
|
||||
setTimeout(function() {
|
||||
serviceRow.classList.remove('is-highlighted');
|
||||
if (serviceRow.className === '') {
|
||||
serviceRow.removeAttribute('class');
|
||||
}
|
||||
}, 3300);
|
||||
if (window.OrganizationDashboard) {
|
||||
window.OrganizationDashboard.highlightAndScrollToService('{{ new_service_id }}');
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
requestAnimationFrame(function() {
|
||||
var form = document.getElementById('create-service-form') ||
|
||||
document.getElementById('invite-user-form');
|
||||
|
||||
if (form) {
|
||||
scrollToElement(form, 50);
|
||||
focusFirstInput(form, 150);
|
||||
}
|
||||
});
|
||||
})();
|
||||
{% if updated_service_id %}
|
||||
if (window.OrganizationDashboard) {
|
||||
window.OrganizationDashboard.highlightAndScrollToService('{{ updated_service_id }}');
|
||||
}
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1711,3 +1711,97 @@ def test_organization_dashboard_services_table(
|
||||
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"
|
||||
|
||||
|
||||
def test_organization_dashboard_shows_edit_service_form(
|
||||
client_request,
|
||||
mock_get_organization,
|
||||
mocker,
|
||||
active_user_with_permissions,
|
||||
):
|
||||
service = service_json(
|
||||
id_=SERVICE_ONE_ID,
|
||||
name="Test Service",
|
||||
restricted=True,
|
||||
active=True,
|
||||
billing_contact_email_addresses="test@example.com",
|
||||
)
|
||||
mocker.patch("app.service_api_client.get_service", return_value={"data": service})
|
||||
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])
|
||||
mocker.patch(
|
||||
"app.organizations_client.get_organization_dashboard",
|
||||
return_value={"services": []},
|
||||
)
|
||||
|
||||
client_request.login(active_user_with_permissions)
|
||||
page = client_request.get(
|
||||
".organization_dashboard",
|
||||
org_id=ORGANISATION_ID,
|
||||
action="edit-service",
|
||||
service_id=SERVICE_ONE_ID,
|
||||
)
|
||||
|
||||
assert page.select_one("#edit-service-form")
|
||||
assert page.select_one("#service_name")["value"] == "service one"
|
||||
assert page.select_one("#primary_contact")
|
||||
assert page.select_one("#statusTrial")["checked"] == ""
|
||||
|
||||
|
||||
def test_organization_dashboard_edit_service_updates_service(
|
||||
client_request,
|
||||
mock_get_organization,
|
||||
mocker,
|
||||
active_user_with_permissions,
|
||||
):
|
||||
service = service_json(
|
||||
id_=SERVICE_ONE_ID,
|
||||
name="Old Name",
|
||||
restricted=True,
|
||||
active=True,
|
||||
billing_contact_email_addresses="old@example.com",
|
||||
)
|
||||
mocker.patch("app.service_api_client.get_service", return_value={"data": service})
|
||||
mock_update_service = mocker.patch("app.service_api_client.update_service")
|
||||
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])
|
||||
mocker.patch(
|
||||
"app.organizations_client.get_organization_dashboard",
|
||||
return_value={"services": []},
|
||||
)
|
||||
mocker.patch("app.extensions.redis_client.delete")
|
||||
|
||||
client_request.login(active_user_with_permissions)
|
||||
client_request.post(
|
||||
".organization_dashboard",
|
||||
org_id=ORGANISATION_ID,
|
||||
action="edit-service",
|
||||
service_id=SERVICE_ONE_ID,
|
||||
_data={
|
||||
"service_name": "New Name",
|
||||
"primary_contact": "new@example.com",
|
||||
"status": "live",
|
||||
},
|
||||
_expected_redirect=url_for(
|
||||
".organization_dashboard",
|
||||
org_id=ORGANISATION_ID,
|
||||
),
|
||||
)
|
||||
|
||||
assert mock_update_service.call_count == 3
|
||||
mock_update_service.assert_any_call(SERVICE_ONE_ID, name="New Name")
|
||||
mock_update_service.assert_any_call(SERVICE_ONE_ID, billing_contact_email_addresses="new@example.com")
|
||||
|
||||
Reference in New Issue
Block a user