mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-07-31 11:49:58 -04:00
Merge pull request #3008 from alphagov/new-choose-service
Split choose service page into live services and trial mode services
This commit is contained in:
@@ -49,6 +49,11 @@
|
||||
margin-top: $gutter / 3;
|
||||
}
|
||||
|
||||
.top-gutter-2-3 {
|
||||
@extend %top-gutter;
|
||||
margin-top: $gutter * 2 / 3;
|
||||
}
|
||||
|
||||
%bottom-gutter,
|
||||
.bottom-gutter {
|
||||
@extend %contain-floats;
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
|
||||
&-hint {
|
||||
@include core-19;
|
||||
margin-top: 5px;
|
||||
margin: 5px 0 10px 0;
|
||||
color: $secondary-text-colour;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from flask import redirect, render_template, session, url_for
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from app import user_api_client
|
||||
from app.main import main
|
||||
from app.utils import PermanentRedirect
|
||||
|
||||
@@ -19,12 +18,8 @@ def services_or_dashboard():
|
||||
@main.route("/accounts")
|
||||
@login_required
|
||||
def choose_account():
|
||||
orgs_and_services = user_api_client.get_organisations_and_services_for_user(current_user)
|
||||
|
||||
return render_template(
|
||||
'views/choose-account.html',
|
||||
organisations=orgs_and_services['organisations'],
|
||||
services_without_organisations=orgs_and_services['services_without_organisations'],
|
||||
can_add_service=current_user.is_gov_user,
|
||||
)
|
||||
|
||||
@@ -36,17 +31,17 @@ def show_accounts_or_dashboard():
|
||||
return redirect(url_for('.index'))
|
||||
|
||||
service_id = session.get('service_id')
|
||||
if service_id and (service_id in current_user.services or current_user.platform_admin):
|
||||
if service_id and (current_user.belongs_to_service(service_id) or current_user.platform_admin):
|
||||
return redirect(url_for('.service_dashboard', service_id=service_id))
|
||||
|
||||
organisation_id = session.get('organisation_id')
|
||||
if organisation_id and (organisation_id in current_user.organisations or current_user.platform_admin):
|
||||
if organisation_id and (current_user.belongs_to_organisation(organisation_id) or current_user.platform_admin):
|
||||
return redirect(url_for('.organisation_dashboard', org_id=organisation_id))
|
||||
|
||||
if len(current_user.services) == 1 and not current_user.organisations:
|
||||
return redirect(url_for('.service_dashboard', service_id=current_user.services[0]))
|
||||
if len(current_user.service_ids) == 1 and not current_user.organisation_ids:
|
||||
return redirect(url_for('.service_dashboard', service_id=current_user.service_ids[0]))
|
||||
|
||||
if len(current_user.organisations) == 1 and not current_user.services:
|
||||
return redirect(url_for('.organisation_dashboard', org_id=current_user.organisations[0]))
|
||||
if len(current_user.organisation_ids) == 1 and not current_user.trial_mode_services:
|
||||
return redirect(url_for('.organisation_dashboard', org_id=current_user.organisation_ids[0]))
|
||||
|
||||
return redirect(url_for('.choose_account'))
|
||||
|
||||
@@ -31,12 +31,9 @@ def find_users_by_email():
|
||||
@login_required
|
||||
@user_is_platform_admin
|
||||
def user_information(user_id):
|
||||
user = User.from_id(user_id)
|
||||
services = user_api_client.get_services_for_user(user)
|
||||
return render_template(
|
||||
'views/find-users/user-information.html',
|
||||
user=user,
|
||||
services=services,
|
||||
user=User.from_id(user_id),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ def submit_request_to_go_live(service_id):
|
||||
volume_sms_formatted=format_if_number(current_service.volume_sms),
|
||||
volume_letter_formatted=format_if_number(current_service.volume_letter),
|
||||
research_consent='Yes' if current_service.consent_to_research else 'No',
|
||||
existing_live='Yes' if user_api_client.user_has_live_services(current_user) else 'No',
|
||||
existing_live='Yes' if current_user.live_services else 'No',
|
||||
email_address=current_user.email_address,
|
||||
),
|
||||
ticket_type=zendesk_client.TYPE_QUESTION,
|
||||
|
||||
@@ -14,7 +14,6 @@ from app import (
|
||||
service_api_client,
|
||||
template_folder_api_client,
|
||||
template_statistics_client,
|
||||
user_api_client,
|
||||
)
|
||||
from app.main import main
|
||||
from app.main.forms import (
|
||||
@@ -127,7 +126,7 @@ def choose_template(service_id, template_type='all', template_folder_id=None):
|
||||
template_type=template_type,
|
||||
allow_adding_letter_template=current_service.has_permission('letter'),
|
||||
allow_adding_copy_of_template=(
|
||||
current_service.all_templates or len(current_user.services) > 1
|
||||
current_service.all_templates or len(current_user.service_ids) > 1
|
||||
),
|
||||
)
|
||||
option_hints = {template_folder_id: 'current folder'}
|
||||
@@ -368,10 +367,7 @@ def choose_template_to_copy(
|
||||
else:
|
||||
return render_template(
|
||||
'views/templates/copy.html',
|
||||
services_templates_and_folders=TemplateLists([
|
||||
Service(service) for service in
|
||||
user_api_client.get_services_for_user(current_user)
|
||||
], user=current_user),
|
||||
services_templates_and_folders=TemplateLists(current_user),
|
||||
search_form=SearchByNameForm(),
|
||||
)
|
||||
|
||||
@@ -402,7 +398,7 @@ def copy_template(service_id, template_id):
|
||||
form=form,
|
||||
template=template,
|
||||
heading_action='Add',
|
||||
services=user_api_client.get_service_ids_for_user(current_user),
|
||||
services=current_user.service_ids,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ class Service(JSONModel):
|
||||
def __init__(self, _dict):
|
||||
|
||||
super().__init__(_dict)
|
||||
|
||||
if 'permissions' not in self._dict:
|
||||
self.permissions = {'email', 'sms', 'letter'}
|
||||
|
||||
@@ -94,6 +93,10 @@ class Service(JSONModel):
|
||||
def trial_mode(self):
|
||||
return self._dict['restricted']
|
||||
|
||||
@property
|
||||
def live(self):
|
||||
return not self.trial_mode
|
||||
|
||||
def has_permission(self, permission):
|
||||
return permission in self.permissions
|
||||
|
||||
|
||||
@@ -65,9 +65,9 @@ class TemplateList():
|
||||
|
||||
class TemplateLists():
|
||||
|
||||
def __init__(self, services, user=None):
|
||||
def __init__(self, user):
|
||||
self.services = sorted(
|
||||
services,
|
||||
user.services,
|
||||
key=lambda service: service.name.lower(),
|
||||
)
|
||||
self.user = user
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from collections.abc import Sequence
|
||||
from itertools import chain
|
||||
|
||||
from flask import abort, current_app, request, session
|
||||
from flask_login import AnonymousUserMixin, UserMixin, login_user
|
||||
@@ -38,11 +39,9 @@ class User(JSONModel, UserMixin):
|
||||
'failed_login_count',
|
||||
'logged_in_at',
|
||||
'mobile_number',
|
||||
'organisations',
|
||||
'password_changed_at',
|
||||
'permissions',
|
||||
'platform_admin',
|
||||
'services',
|
||||
'state',
|
||||
}
|
||||
|
||||
@@ -193,9 +192,9 @@ class User(JSONModel, UserMixin):
|
||||
return True
|
||||
|
||||
if org_id:
|
||||
return org_id in self.organisations
|
||||
return org_id in self.organisation_ids
|
||||
if not permissions:
|
||||
return service_id in self.services
|
||||
return service_id in self.service_ids
|
||||
if service_id:
|
||||
return any(x in self._permissions.get(service_id, []) for x in permissions)
|
||||
|
||||
@@ -223,12 +222,15 @@ class User(JSONModel, UserMixin):
|
||||
]
|
||||
|
||||
def belongs_to_service(self, service_id):
|
||||
return str(service_id) in self.services
|
||||
return str(service_id) in self.service_ids
|
||||
|
||||
def belongs_to_service_or_403(self, service_id):
|
||||
if not self.belongs_to_service(service_id):
|
||||
abort(403)
|
||||
|
||||
def belongs_to_organisation(self, organisation_id):
|
||||
return str(organisation_id) in self.organisation_ids
|
||||
|
||||
@property
|
||||
def locked(self):
|
||||
return self.failed_login_count >= self.max_failed_login_count
|
||||
@@ -237,6 +239,71 @@ class User(JSONModel, UserMixin):
|
||||
def email_domain(self):
|
||||
return self.email_address.split('@')[-1]
|
||||
|
||||
@cached_property
|
||||
def orgs_and_services(self):
|
||||
return user_api_client.get_organisations_and_services_for_user(self.id)
|
||||
|
||||
@property
|
||||
def services(self):
|
||||
return sorted(
|
||||
self.services_with_organisation + self.services_without_organisations,
|
||||
key=lambda service: service.name.lower(),
|
||||
)
|
||||
|
||||
@property
|
||||
def services_with_organisation(self):
|
||||
from app.models.service import Service
|
||||
return [
|
||||
Service(service) for service in
|
||||
next(chain(
|
||||
org['services'] for org in self.orgs_and_services['organisations']
|
||||
), [])
|
||||
]
|
||||
|
||||
@property
|
||||
def services_without_organisations(self):
|
||||
from app.models.service import Service
|
||||
return [
|
||||
Service(service) for service in
|
||||
self.orgs_and_services['services_without_organisations']
|
||||
]
|
||||
|
||||
@property
|
||||
def service_ids(self):
|
||||
return self._dict['services']
|
||||
|
||||
@property
|
||||
def trial_mode_services(self):
|
||||
return [
|
||||
service for service in self.services if service.trial_mode
|
||||
]
|
||||
|
||||
@property
|
||||
def live_services(self):
|
||||
return [
|
||||
service for service in self.services if service.live
|
||||
]
|
||||
|
||||
@property
|
||||
def live_services_not_belonging_to_users_organisations(self):
|
||||
from app.models.service import Service
|
||||
return [
|
||||
Service(service)
|
||||
for service in self.orgs_and_services['services_without_organisations']
|
||||
if not service['restricted']
|
||||
]
|
||||
|
||||
@property
|
||||
def organisations(self):
|
||||
return [
|
||||
Organisation.from_id(organisation['id'])
|
||||
for organisation in self.orgs_and_services['organisations']
|
||||
]
|
||||
|
||||
@property
|
||||
def organisation_ids(self):
|
||||
return self._dict['organisations']
|
||||
|
||||
@cached_property
|
||||
def default_organisation(self):
|
||||
return Organisation(
|
||||
@@ -251,6 +318,14 @@ class User(JSONModel, UserMixin):
|
||||
return 'nhs'
|
||||
return None
|
||||
|
||||
@property
|
||||
def has_access_to_live_and_trial_mode_services(self):
|
||||
return (
|
||||
self.organisations or self.live_services
|
||||
) and (
|
||||
self.trial_mode_services
|
||||
)
|
||||
|
||||
@property
|
||||
def has_nhs_email_address(self):
|
||||
return self.email_address.lower().endswith((
|
||||
@@ -267,7 +342,7 @@ class User(JSONModel, UserMixin):
|
||||
"state": self.state,
|
||||
"failed_login_count": self.failed_login_count,
|
||||
"permissions": [x for x in self._permissions],
|
||||
"organisations": self.organisations,
|
||||
"organisations": self.organisation_ids,
|
||||
"current_session_id": self.current_session_id
|
||||
}
|
||||
if hasattr(self, '_password'):
|
||||
|
||||
@@ -89,7 +89,6 @@ class HeaderNavigation(Navigation):
|
||||
'letter_branding',
|
||||
'live_services',
|
||||
'live_services_csv',
|
||||
'organisations',
|
||||
'performance_platform_xlsx',
|
||||
'platform_admin',
|
||||
'platform_admin_letter_validation_preview',
|
||||
@@ -214,6 +213,7 @@ class HeaderNavigation(Navigation):
|
||||
'organisation_settings',
|
||||
'organisation_preview_email_branding',
|
||||
'organisation_preview_letter_branding',
|
||||
'organisations',
|
||||
'privacy',
|
||||
'public_agreement',
|
||||
'public_download_agreement',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from itertools import chain
|
||||
|
||||
from notifications_python_client.errors import HTTPError
|
||||
|
||||
from app.models.roles_and_permissions import (
|
||||
@@ -184,29 +182,9 @@ class UserApiClient(NotifyAdminAPIClient):
|
||||
data = {'email': new_email}
|
||||
self.post(endpoint, data)
|
||||
|
||||
def get_organisations_and_services_for_user(self, user):
|
||||
endpoint = '/user/{}/organisations-and-services'.format(user.id)
|
||||
def get_organisations_and_services_for_user(self, user_id):
|
||||
endpoint = '/user/{}/organisations-and-services'.format(user_id)
|
||||
return self.get(endpoint)
|
||||
|
||||
def get_services_for_user(self, user):
|
||||
orgs_and_services_for_user = self.get_organisations_and_services_for_user(user)
|
||||
all_services = orgs_and_services_for_user['services_without_organisations'] + next(chain(
|
||||
org['services'] for org in orgs_and_services_for_user['organisations']
|
||||
), [])
|
||||
return sorted(all_services, key=lambda service: service['name'])
|
||||
|
||||
def user_has_live_services(self, user):
|
||||
return any(
|
||||
not service['restricted'] for service in self.get_services_for_user(user)
|
||||
)
|
||||
|
||||
def get_service_ids_for_user(self, user):
|
||||
return {
|
||||
service['id'] for service in self.get_services_for_user(user)
|
||||
}
|
||||
|
||||
def user_belongs_to_service(self, user, service_id):
|
||||
return str(service_id) in self.get_service_ids_for_user(user)
|
||||
|
||||
|
||||
user_api_client = UserApiClient()
|
||||
|
||||
@@ -1,43 +1,119 @@
|
||||
{% extends "withoutnav_template.html" %}
|
||||
|
||||
{% macro service_list(
|
||||
heading,
|
||||
show_heading,
|
||||
organisations=[],
|
||||
services=[]
|
||||
) %}
|
||||
{% if show_heading %}
|
||||
<div class="grid-row">
|
||||
<div class="column-one-quarter">
|
||||
<h2>
|
||||
{{ heading }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="column-three-quarters">
|
||||
<ul>
|
||||
{% else %}
|
||||
<ul>
|
||||
{% endif %}
|
||||
{% for org in organisations %}
|
||||
<li class="browse-list-item">
|
||||
<a href="{{ url_for('.organisation_dashboard', org_id=org.id) }}" class="browse-list-link">{{ org.name }}</a>
|
||||
<p class="browse-list-hint">
|
||||
{{ org.live_services|length }}
|
||||
live service{% if org.live_services|length != 1 %}s{% endif %}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% for service in services %}
|
||||
<li class="browse-list-item">
|
||||
<a href="{{ url_for('.service_dashboard', service_id=service.id) }}" class="browse-list-link">{{ service.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if show_heading %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if show_add_service_button %}
|
||||
<div class="js-stick-at-bottom-when-scrolling">
|
||||
<a href="{{ url_for('.add_service') }}" class="button-secondary">Add a new service</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="keyline-block"></div>
|
||||
{% endmacro %}
|
||||
|
||||
{% block per_page_title %}
|
||||
Choose service
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-large">
|
||||
<h1 class="heading-large {% if current_user.has_access_to_live_and_trial_mode_services %}visually-hidden{% endif %}">
|
||||
Choose service
|
||||
</h1>
|
||||
<nav class="browse-list">
|
||||
<ul>
|
||||
{% for org in organisations %}
|
||||
<li class="browse-list-item">
|
||||
<a href="{{ url_for('.organisation_dashboard', org_id=org.id) }}" class="browse-list-link">{{ org.name }}</a>
|
||||
{% if org.services %}
|
||||
<ul class="browse-sub-list">
|
||||
{% for item in org.services %}
|
||||
<li class="browse-list-sub-item">
|
||||
<a href="{{ url_for('.service_dashboard', service_id=item.id) }}" class="browse-list-link">{{ item.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
<div class ="keyline-block"></div>
|
||||
{% endfor %}
|
||||
{% if services_without_organisations %}
|
||||
{% for item in services_without_organisations %}
|
||||
|
||||
<nav class="browse-list {% if current_user.has_access_to_live_and_trial_mode_services %}top-gutter-2-3{% endif %}">
|
||||
|
||||
{% if current_user.platform_admin %}
|
||||
<div class="grid-row">
|
||||
<div class="column-one-quarter">
|
||||
<h2>
|
||||
Platform admin
|
||||
</h2>
|
||||
</div>
|
||||
<ul class="column-three-quarters">
|
||||
<li class="browse-list-item">
|
||||
<a href="{{ url_for('.service_dashboard', service_id=item.id) }}" class="browse-list-link">{{ item.name }}</a>
|
||||
<a href="{{ url_for('.organisations') }}" class="browse-list-link">All organisations</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<div class ="keyline-block"></div>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="keyline-block"></div>
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.organisations %}
|
||||
{{ service_list(
|
||||
heading='Live services',
|
||||
show_heading=current_user.trial_mode_services,
|
||||
organisations=current_user.organisations,
|
||||
services=current_user.live_services_not_belonging_to_users_organisations
|
||||
) }}
|
||||
{% else %}
|
||||
{{ service_list(
|
||||
heading='Live services',
|
||||
show_heading=(current_user.trial_mode_services and current_user.live_services),
|
||||
services=current_user.live_services
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.trial_mode_services %}
|
||||
{{ service_list(
|
||||
heading='Trial mode services',
|
||||
show_heading=(current_user.organisations or current_user.live_services),
|
||||
services=current_user.trial_mode_services
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
</nav>
|
||||
|
||||
{% if can_add_service %}
|
||||
<div class="js-stick-at-bottom-when-scrolling">
|
||||
{% if current_user.has_access_to_live_and_trial_mode_services %}
|
||||
<div class="grid-row">
|
||||
<div class="column-one-quarter">
|
||||
|
||||
</div>
|
||||
<div class="column-three-quarters">
|
||||
{% endif %}
|
||||
<a href="{{ url_for('.add_service') }}" class="button-secondary">Add a new service</a>
|
||||
{% if current_user.has_access_to_live_and_trial_mode_services %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -12,20 +12,42 @@
|
||||
{{ user.name }}
|
||||
</h1>
|
||||
<p>{{ user.email_address }}</p>
|
||||
<p>{{ user.mobile_number }}</p>
|
||||
<h2 class="heading-medium">Services</h2>
|
||||
<p class="{{ '' if user.mobile_number else 'hint' }}">{{ user.mobile_number or 'No mobile number'}}</p>
|
||||
<h2 class="heading-medium">Live services</h2>
|
||||
<nav class="browse-list">
|
||||
<ul>
|
||||
{% for service in services %}
|
||||
<li class="browse-list-item">
|
||||
<a class="browse-list-hint" href={{url_for('.service_dashboard', service_id=service.id)}}>{{ service.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if user.live_services %}
|
||||
<ul>
|
||||
{% for service in user.live_services %}
|
||||
<li class="browse-list-item">
|
||||
<a class="browse-list-hint" href={{url_for('.service_dashboard', service_id=service.id)}}>{{ service.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="hint">
|
||||
No live services
|
||||
</p>
|
||||
{% endif %}
|
||||
</nav>
|
||||
<h2 class="heading-medium">Trial mode services</h2>
|
||||
<nav class="browse-list">
|
||||
{% if user.trial_mode_services %}
|
||||
<ul>
|
||||
{% for service in user.trial_mode_services %}
|
||||
<li class="browse-list-item">
|
||||
<a class="browse-list-hint" href={{url_for('.service_dashboard', service_id=service.id)}}>{{ service.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="hint">
|
||||
No services in trial mode
|
||||
</p>
|
||||
{% endif %}
|
||||
</nav>
|
||||
<h2 class="heading-medium">Last login</h2>
|
||||
{% if not user.logged_in_at %}
|
||||
<p>This person has never logged in</p>
|
||||
<p class="hint">This person has never logged in</p>
|
||||
{% else %}
|
||||
<p>Last logged in
|
||||
<time class="timeago" datetime="{{ user.logged_in_at }}">
|
||||
|
||||
@@ -1,35 +1,59 @@
|
||||
{% extends "views/platform-admin/_base_template.html" %}
|
||||
{% extends "withoutnav_template.html" %}
|
||||
{% from "components/live-search.html" import live_search %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
{% from "components/page-header.html" import page_header %}
|
||||
|
||||
{% block per_page_title %}
|
||||
Organisations
|
||||
All organisations
|
||||
{% endblock %}
|
||||
|
||||
{% block org_page_title %}
|
||||
Organisations
|
||||
{% endblock %}
|
||||
{% block fullwidth_content %}
|
||||
|
||||
{% block platform_admin_content %}
|
||||
<div id="content">
|
||||
<div class="navigation-service">
|
||||
<div class="navigation-service-name">
|
||||
|
||||
</div>
|
||||
<a href="{{ url_for('main.choose_account') }}" class="navigation-service-switch">Switch service</a>
|
||||
</div>
|
||||
|
||||
<h1 class="heading-large">
|
||||
Organisations
|
||||
</h1>
|
||||
{{ live_search(target_selector='.browse-list-item', show=True, form=search_form, label='Search by name') }}
|
||||
<nav class="browse-list">
|
||||
<ul>
|
||||
{% for org in organisations %}
|
||||
<li class="browse-list-item">
|
||||
<a href="{{ url_for('main.organisation_dashboard', org_id=org['id']) }}" class="browse-list-link">{{ org['name'] }}</a>
|
||||
{% if not org['active'] %}
|
||||
<span class="table-field-status-default heading-medium">- archived</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="js-stick-at-bottom-when-scrolling">
|
||||
<a href="{{ url_for('main.add_organisation') }}" class="button-secondary">New organisation</a>
|
||||
<main role="main">
|
||||
<div class="grid-row top-gutter-2-3">
|
||||
<div class="column-one-quarter">
|
||||
<h1>
|
||||
All organisations
|
||||
</h1>
|
||||
</div>
|
||||
<div class="column-three-quarters">
|
||||
|
||||
{{ live_search(target_selector='.browse-list-item', show=True, form=search_form, label='Search by name') }}
|
||||
|
||||
<nav class="browse-list">
|
||||
<ul>
|
||||
{% for org in organisations %}
|
||||
<li class="browse-list-item">
|
||||
<a href="{{ url_for('main.organisation_dashboard', org_id=org['id']) }}" class="browse-list-link">{{ org['name'] }}</a>
|
||||
{% if not org['active'] %}
|
||||
<span class="table-field-status-default heading-medium">- archived</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="js-stick-at-bottom-when-scrolling">
|
||||
<div class="grid-row">
|
||||
<div class="column-one-quarter">
|
||||
|
||||
</div>
|
||||
<div class="column-three-quarters">
|
||||
<a href="{{ url_for('main.add_organisation') }}" class="button-secondary">New organisation</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
('Summary', url_for('main.platform_admin')),
|
||||
('Live services', url_for('main.live_services')),
|
||||
('Trial mode services', url_for('main.trial_services')),
|
||||
('Organisations', url_for('main.organisations')),
|
||||
('Providers', url_for('main.view_providers')),
|
||||
('Reports', url_for('main.platform_admin_reports')),
|
||||
('Email branding', url_for('main.email_branding')),
|
||||
|
||||
@@ -15,16 +15,16 @@ SAMPLE_DATA = {
|
||||
'name': 'org_1',
|
||||
'id': 'o1',
|
||||
'services': [
|
||||
{'name': 'org_service_1', 'id': 'os1'},
|
||||
{'name': 'org_service_2', 'id': 'os2'},
|
||||
{'name': 'org_service_3', 'id': 'os3'},
|
||||
{'name': 'org_service_1', 'id': 'os1', 'restricted': False},
|
||||
{'name': 'org_service_2', 'id': 'os2', 'restricted': False},
|
||||
{'name': 'org_service_3', 'id': 'os3', 'restricted': True},
|
||||
]
|
||||
},
|
||||
{
|
||||
'name': 'org_2',
|
||||
'id': 'o2',
|
||||
'services': [
|
||||
{'name': 'org_service_4', 'id': 'os4'},
|
||||
{'name': 'org_service_4', 'id': 'os4', 'restricted': False},
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -34,9 +34,9 @@ SAMPLE_DATA = {
|
||||
}
|
||||
],
|
||||
'services_without_organisations': [
|
||||
{'name': 'service_1', 'id': 's1'},
|
||||
{'name': 'service_2', 'id': 's2'},
|
||||
{'name': 'service_3', 'id': 's3'},
|
||||
{'name': 'service_1', 'id': 's1', 'restricted': False},
|
||||
{'name': 'service_2', 'id': 's2', 'restricted': False},
|
||||
{'name': 'service_3', 'id': 's3', 'restricted': True},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -51,45 +51,58 @@ def mock_get_orgs_and_services(mocker):
|
||||
|
||||
def test_choose_account_should_show_choose_accounts_page(
|
||||
client_request,
|
||||
mock_get_orgs_and_services
|
||||
mock_get_orgs_and_services,
|
||||
mock_get_organisation,
|
||||
mock_get_organisation_services,
|
||||
):
|
||||
resp = client_request.get('main.choose_account')
|
||||
page = resp.find('div', {'id': 'content'}).main
|
||||
|
||||
assert normalize_spaces(page.h1.text) == 'Choose service'
|
||||
outer_list_items = page.nav.ul.find_all('li', recursive=False)
|
||||
|
||||
assert len(outer_list_items) == 6
|
||||
outer_list_items = page.select('nav ul')[0].select('li')
|
||||
assert len(outer_list_items) == 5
|
||||
|
||||
# first org
|
||||
assert outer_list_items[0].a.text == 'org_1'
|
||||
assert outer_list_items[0].a.text == 'Org 1'
|
||||
assert outer_list_items[0].a['href'] == url_for('.organisation_dashboard', org_id='o1')
|
||||
outer_list_orgs = outer_list_items[0].ul
|
||||
assert ' '.join(outer_list_orgs.stripped_strings) == 'org_service_1 org_service_2 org_service_3'
|
||||
assert normalize_spaces(outer_list_items[0].select_one('.browse-list-hint').text) == (
|
||||
'1 live service'
|
||||
)
|
||||
|
||||
# second org
|
||||
assert outer_list_items[1].a.text == 'org_2'
|
||||
assert outer_list_items[1].a.text == 'Org 2'
|
||||
assert outer_list_items[1].a['href'] == url_for('.organisation_dashboard', org_id='o2')
|
||||
outer_list_orgs = outer_list_items[1].ul
|
||||
assert ' '.join(outer_list_orgs.stripped_strings) == 'org_service_4'
|
||||
assert normalize_spaces(outer_list_items[1].select_one('.browse-list-hint').text) == (
|
||||
'2 live services'
|
||||
)
|
||||
|
||||
# third org
|
||||
assert outer_list_items[2].a.text == 'org_3'
|
||||
assert outer_list_items[2].a.text == 'Org 3'
|
||||
assert outer_list_items[2].a['href'] == url_for('.organisation_dashboard', org_id='o3')
|
||||
assert not outer_list_items[2].ul # org 3 has no services
|
||||
assert normalize_spaces(outer_list_items[2].select_one('.browse-list-hint').text) == (
|
||||
'0 live services'
|
||||
)
|
||||
|
||||
# orphaned services
|
||||
# orphaned live services
|
||||
assert outer_list_items[3].a.text == 'service_1'
|
||||
assert outer_list_items[3].a['href'] == url_for('.service_dashboard', service_id='s1')
|
||||
assert outer_list_items[4].a.text == 'service_2'
|
||||
assert outer_list_items[4].a['href'] == url_for('.service_dashboard', service_id='s2')
|
||||
assert outer_list_items[5].a.text == 'service_3'
|
||||
assert outer_list_items[5].a['href'] == url_for('.service_dashboard', service_id='s3')
|
||||
|
||||
# orphaned trial services
|
||||
trial_services_list_items = page.select('nav ul')[1].select('li')
|
||||
assert len(trial_services_list_items) == 2
|
||||
assert trial_services_list_items[0].a.text == 'org_service_3'
|
||||
assert trial_services_list_items[0].a['href'] == url_for('.service_dashboard', service_id='os3')
|
||||
assert trial_services_list_items[1].a.text == 'service_3'
|
||||
assert trial_services_list_items[1].a['href'] == url_for('.service_dashboard', service_id='s3')
|
||||
|
||||
|
||||
def test_choose_account_should_show_choose_accounts_page_if_no_services(
|
||||
client_request,
|
||||
mock_get_orgs_and_services
|
||||
mock_get_orgs_and_services,
|
||||
mock_get_organisation,
|
||||
mock_get_organisation_services,
|
||||
):
|
||||
mock_get_orgs_and_services.return_value = {
|
||||
'organisations': [],
|
||||
@@ -106,9 +119,27 @@ def test_choose_account_should_show_choose_accounts_page_if_no_services(
|
||||
assert add_service_link['href'] == url_for('main.add_service')
|
||||
|
||||
|
||||
def test_choose_account_should_should_organisations_link_for_platform_admin(
|
||||
client_request,
|
||||
platform_admin_user,
|
||||
mock_get_orgs_and_services,
|
||||
mock_get_organisation,
|
||||
mock_get_organisation_services,
|
||||
):
|
||||
client_request.login(platform_admin_user)
|
||||
|
||||
page = client_request.get('main.choose_account')
|
||||
|
||||
first_link = page.select_one('.browse-list-item a')
|
||||
assert first_link.text == 'All organisations'
|
||||
assert first_link['href'] == url_for('main.organisations')
|
||||
|
||||
|
||||
def test_choose_account_should_show_back_to_service_link(
|
||||
client_request,
|
||||
mock_get_orgs_and_services
|
||||
mock_get_orgs_and_services,
|
||||
mock_get_organisation,
|
||||
mock_get_organisation_services,
|
||||
):
|
||||
resp = client_request.get('main.choose_account')
|
||||
|
||||
@@ -122,7 +153,9 @@ def test_choose_account_should_show_back_to_service_link(
|
||||
def test_choose_account_should_not_show_back_to_service_link_if_no_service_in_session(
|
||||
client,
|
||||
client_request,
|
||||
mock_get_orgs_and_services
|
||||
mock_get_orgs_and_services,
|
||||
mock_get_organisation,
|
||||
mock_get_organisation_services,
|
||||
):
|
||||
with client.session_transaction() as session:
|
||||
session['service_id'] = None
|
||||
@@ -152,6 +185,8 @@ def test_choose_account_should_not_show_back_to_service_link_if_service_archived
|
||||
client_request,
|
||||
service_one,
|
||||
mock_get_orgs_and_services,
|
||||
mock_get_organisation,
|
||||
mock_get_organisation_services,
|
||||
active,
|
||||
):
|
||||
service_one['active'] = active
|
||||
|
||||
@@ -16,7 +16,12 @@ def user_with_orgs_and_services(num_orgs, num_services, platform_admin=False):
|
||||
@pytest.mark.parametrize('num_orgs,num_services,endpoint,endpoint_kwargs', [
|
||||
(0, 0, '.choose_account', {}),
|
||||
(0, 2, '.choose_account', {}),
|
||||
(1, 1, '.choose_account', {}),
|
||||
|
||||
# assumption is that live service is part of user’s organisation
|
||||
# – real users shouldn’t have orphaned live services, or access to
|
||||
# services belonging to other organisations
|
||||
(1, 1, '.organisation_dashboard', {'org_id': 'org1'}),
|
||||
|
||||
(2, 0, '.choose_account', {}),
|
||||
(0, 1, '.service_dashboard', {'service_id': 'service1'}),
|
||||
(1, 0, '.organisation_dashboard', {'org_id': 'org1'}),
|
||||
@@ -24,6 +29,7 @@ def user_with_orgs_and_services(num_orgs, num_services, platform_admin=False):
|
||||
def test_show_accounts_or_dashboard_redirects_to_choose_account_or_service_dashboard(
|
||||
client,
|
||||
mocker,
|
||||
mock_get_non_empty_organisations_and_services_for_user,
|
||||
num_orgs,
|
||||
num_services,
|
||||
endpoint,
|
||||
@@ -72,6 +78,7 @@ def test_show_accounts_or_dashboard_redirects_if_org_in_session(client, mocker):
|
||||
def test_show_accounts_or_dashboard_doesnt_redirect_to_service_dashboard_if_user_not_part_of_service_in_session(
|
||||
client,
|
||||
mocker,
|
||||
mock_get_non_empty_organisations_and_services_for_user,
|
||||
mock_get_service
|
||||
):
|
||||
client.login(user_with_orgs_and_services(num_orgs=1, num_services=1), mocker=mocker)
|
||||
@@ -82,12 +89,13 @@ def test_show_accounts_or_dashboard_doesnt_redirect_to_service_dashboard_if_user
|
||||
response = client.get(url_for('.show_accounts_or_dashboard'))
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('main.choose_account', _external=True)
|
||||
assert response.location == url_for('main.organisation_dashboard', org_id='org1', _external=True)
|
||||
|
||||
|
||||
def test_show_accounts_or_dashboard_doesnt_redirect_to_org_dashboard_if_user_not_part_of_org_in_session(
|
||||
client,
|
||||
mocker
|
||||
mocker,
|
||||
mock_get_non_empty_organisations_and_services_for_user,
|
||||
):
|
||||
client.login(user_with_orgs_and_services(num_orgs=1, num_services=1), mocker=mocker)
|
||||
with client.session_transaction() as session:
|
||||
@@ -97,7 +105,7 @@ def test_show_accounts_or_dashboard_doesnt_redirect_to_org_dashboard_if_user_not
|
||||
response = client.get(url_for('.show_accounts_or_dashboard'))
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('main.choose_account', _external=True)
|
||||
assert response.location == url_for('main.organisation_dashboard', org_id='org1', _external=True)
|
||||
|
||||
|
||||
def test_show_accounts_or_dashboard_redirects_if_not_logged_in(
|
||||
|
||||
@@ -37,7 +37,7 @@ def test_organisation_page_shows_all_organisations(
|
||||
|
||||
assert normalize_spaces(
|
||||
page.select_one('h1').text
|
||||
) == "Organisations"
|
||||
) == "All organisations"
|
||||
|
||||
for index, org in enumerate(orgs):
|
||||
assert page.select('a.browse-list-link')[index].text == org['name']
|
||||
|
||||
@@ -101,8 +101,8 @@ def test_user_information_page_shows_information_about_user(
|
||||
mocker.patch(
|
||||
'app.user_api_client.get_organisations_and_services_for_user',
|
||||
return_value={'organisations': [], 'services_without_organisations': [
|
||||
{"id": 1, "name": "Fresh Orchard Juice"},
|
||||
{"id": 2, "name": "Nature Therapy"},
|
||||
{"id": 1, "name": "Fresh Orchard Juice", "restricted": True},
|
||||
{"id": 2, "name": "Nature Therapy", "restricted": False},
|
||||
]},
|
||||
autospec=True
|
||||
)
|
||||
@@ -116,10 +116,12 @@ def test_user_information_page_shows_information_about_user(
|
||||
assert document.xpath("//p/text()[normalize-space()='test@gov.uk']")
|
||||
assert document.xpath("//p/text()[normalize-space()='+447700900986']")
|
||||
|
||||
assert document.xpath("//h2/text()[normalize-space()='Services']")
|
||||
assert document.xpath("//a/text()[normalize-space()='Fresh Orchard Juice']")
|
||||
assert document.xpath("//h2/text()[normalize-space()='Live services']")
|
||||
assert document.xpath("//a/text()[normalize-space()='Nature Therapy']")
|
||||
|
||||
assert document.xpath("//h2/text()[normalize-space()='Trial mode services']")
|
||||
assert document.xpath("//a/text()[normalize-space()='Fresh Orchard Juice']")
|
||||
|
||||
assert document.xpath("//h2/text()[normalize-space()='Last login']")
|
||||
assert not document.xpath("//p/text()[normalize-space()='0 failed login attempts']")
|
||||
|
||||
@@ -137,8 +139,8 @@ def test_user_information_page_displays_if_there_are_failed_login_attempts(
|
||||
mocker.patch(
|
||||
'app.user_api_client.get_organisations_and_services_for_user',
|
||||
return_value={'organisations': [], 'services_without_organisations': [
|
||||
{"id": 1, "name": "Fresh Orchard Juice"},
|
||||
{"id": 2, "name": "Nature Therapy"},
|
||||
{"id": 1, "name": "Fresh Orchard Juice", "restricted": True},
|
||||
{"id": 2, "name": "Nature Therapy", "restricted": True},
|
||||
]},
|
||||
autospec=True
|
||||
)
|
||||
|
||||
@@ -3136,7 +3136,11 @@ def mock_get_organisation(
|
||||
def _get_organisation(org_id):
|
||||
return organisation_json(
|
||||
org_id,
|
||||
'Org 1',
|
||||
{
|
||||
'o1': 'Org 1',
|
||||
'o2': 'Org 2',
|
||||
'o3': 'Org 3',
|
||||
}.get(org_id, 'Org 1'),
|
||||
email_branding_id=email_branding_id,
|
||||
letter_branding_id=letter_branding_id,
|
||||
)
|
||||
@@ -3199,6 +3203,18 @@ def mock_update_service_organisation(mocker):
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_organisation_services(mocker, api_user_active):
|
||||
def _get_organisation_services(organisation_id):
|
||||
if organisation_id == 'o1':
|
||||
return [
|
||||
service_json('12345', 'service one', restricted=False),
|
||||
service_json('67890', 'service two'),
|
||||
service_json('abcde', 'service three'),
|
||||
]
|
||||
if organisation_id == 'o2':
|
||||
return [
|
||||
service_json('12345', 'service one', restricted=False),
|
||||
service_json('67890', 'service two', restricted=False),
|
||||
service_json('abcde', 'service three'),
|
||||
]
|
||||
return [
|
||||
service_json('12345', 'service one'),
|
||||
service_json('67890', 'service two'),
|
||||
@@ -3339,6 +3355,7 @@ def mock_get_non_empty_organisations_and_services_for_user(mocker, organisation_
|
||||
return [{
|
||||
'name': '{} {}'.format(name, i),
|
||||
'id': SERVICE_TWO_ID,
|
||||
'restricted': False,
|
||||
} for i in range(1, 3)]
|
||||
|
||||
def _get_orgs_and_services(user_id):
|
||||
|
||||
Reference in New Issue
Block a user