Merge pull request #3085 from cisagov/dk/2730-domain-overview

#2730: domain management overview page
This commit is contained in:
dave-kennedy-ecs 2024-11-27 15:37:37 -05:00 committed by GitHub
commit 9e190dae70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 288 additions and 10 deletions

View file

@ -1,5 +1,6 @@
@use "uswds-core" as *;
@use "cisa_colors" as *;
@use "typography" as *;
.usa-form .usa-button {
margin-top: units(3);
@ -69,9 +70,9 @@ legend.float-left-tablet + button.float-right-tablet {
}
.read-only-label {
font-size: size('body', 'sm');
@extend .h4--sm-05;
font-weight: bold;
color: color('primary-dark');
margin-bottom: units(0.5);
}
.read-only-value {

View file

@ -23,6 +23,13 @@ h2 {
color: color('primary-darker');
}
.h4--sm-05 {
font-size: size('body', 'sm');
font-weight: normal;
color: color('primary');
margin-bottom: units(0.5);
}
// Normalize typography in forms
.usa-form,
.usa-form fieldset {

View file

@ -68,6 +68,7 @@ def portfolio_permissions(request):
"has_organization_feature_flag": False,
"has_organization_requests_flag": False,
"has_organization_members_flag": False,
"is_portfolio_admin": False,
}
try:
portfolio = request.session.get("portfolio")
@ -88,6 +89,7 @@ def portfolio_permissions(request):
"has_organization_feature_flag": True,
"has_organization_requests_flag": request.user.has_organization_requests_flag(),
"has_organization_members_flag": request.user.has_organization_members_flag(),
"is_portfolio_admin": request.user.is_portfolio_admin(portfolio),
}
return portfolio_context

View file

@ -258,6 +258,9 @@ class User(AbstractUser):
def has_edit_suborganization_portfolio_permission(self, portfolio):
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.EDIT_SUBORGANIZATION)
def is_portfolio_admin(self, portfolio):
return "Admin" in self.portfolio_role_summary(portfolio)
def get_first_portfolio(self):
permission = self.portfolio_permissions.first()
if permission:

View file

@ -5,6 +5,25 @@
{% block domain_content %}
{% block breadcrumb %}
{% if portfolio %}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain' pk=domain.id %}" class="usa-breadcrumb__link"><span>{{ domain.name }}</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain-users' pk=domain.id %}" class="usa-breadcrumb__link"><span>Domain managers</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>Add a domain manager</span>
</li>
</ol>
</nav>
{% else %}
{% url 'domain-users' pk=domain.id as url %}
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain manager breadcrumb">
<ol class="usa-breadcrumb__list">
@ -16,6 +35,7 @@
</li>
</ol>
</nav>
{% endif %}
{% endblock breadcrumb %}
<h1>Add a domain manager</h1>
{% if has_organization_feature_flag %}

View file

@ -3,6 +3,22 @@
{% load custom_filters %}
{% block domain_content %}
{% block breadcrumb %}
{% if portfolio %}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>{{ domain.name }}</span>
</li>
</ol>
</nav>
{% endif %}
{% endblock breadcrumb %}
{{ block.super }}
<div class="margin-top-4 tablet:grid-col-10">
<h2 class="text-bold text-primary-dark domain-name-wrap">{{ domain.name }}</h2>
@ -74,13 +90,17 @@
{% include "includes/summary_item.html" with title='DNSSEC' value='Not Enabled' edit_link=url editable=is_editable %}
{% endif %}
{% if portfolio and has_any_domains_portfolio_permission and has_view_suborganization_portfolio_permission %}
{% if portfolio %}
{% if has_any_domains_portfolio_permission and has_edit_suborganization_portfolio_permission %}
{% url 'domain-suborganization' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link=url editable=is_editable|and:has_edit_suborganization_portfolio_permission %}
{% elif has_any_domains_portfolio_permission and has_view_suborganization_portfolio_permission %}
{% url 'domain-suborganization' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link=url editable=is_editable|and:has_view_suborganization_portfolio_permission view_button=True %}
{% endif %}
{% else %}
{% url 'domain-org-name-address' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Organization' value=domain.domain_info address='true' edit_link=url editable=is_editable %}
{% url 'domain-senior-official' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Senior official' value=domain.domain_info.senior_official contact='true' edit_link=url editable=is_editable %}
{% endif %}
@ -92,7 +112,11 @@
{% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url editable=is_editable %}
{% endif %}
{% url 'domain-users' pk=domain.id as url %}
{% include "includes/summary_item.html" with title='Domain managers' users='true' list=True value=domain.permissions.all edit_link=url editable=is_editable %}
{% if portfolio %}
{% include "includes/summary_item.html" with title='Domain managers' domain_permissions=True value=domain edit_link=url editable=is_editable %}
{% else %}
{% include "includes/summary_item.html" with title='Domain managers' list=True users=True value=domain.permissions.all edit_link=url editable=is_editable %}
{% endif %}
</div>
{% endblock %} {# domain_content #}

View file

@ -4,6 +4,24 @@
{% block title %}DNS | {{ domain.name }} | {% endblock %}
{% block domain_content %}
{% block breadcrumb %}
{% if portfolio %}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain' pk=domain.id %}" class="usa-breadcrumb__link"><span>{{ domain.name }}</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>DNS</span>
</li>
</ol>
</nav>
{% endif %}
{% endblock breadcrumb %}
<h1>DNS</h1>

View file

@ -5,6 +5,28 @@
{% block domain_content %}
{% block breadcrumb %}
{% if portfolio %}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain' pk=domain.id %}" class="usa-breadcrumb__link"><span>{{ domain.name }}</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain-dns' pk=domain.id %}" class="usa-breadcrumb__link"><span>DNS</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>DNSSEC</span>
</li>
</ol>
</nav>
{% endif %}
{% endblock breadcrumb %}
<h1>DNSSEC</h1>
<p>DNSSEC, or DNS Security Extensions, is an additional security layer to protect your website. Enabling DNSSEC ensures that when someone visits your domain, they can be certain that its connecting to the correct server, preventing potential hijacking or tampering with your domain's records.</p>

View file

@ -4,6 +4,32 @@
{% block title %}DS data | {{ domain.name }} | {% endblock %}
{% block domain_content %}
{% block breadcrumb %}
{% if portfolio %}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain' pk=domain.id %}" class="usa-breadcrumb__link"><span>{{ domain.name }}</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain-dns' pk=domain.id %}" class="usa-breadcrumb__link"><span>DNS</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain-dns-dnssec' pk=domain.id %}" class="usa-breadcrumb__link"><span>DNSSEC</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>DS data</span>
</li>
</ol>
</nav>
{% endif %}
{% endblock breadcrumb %}
{% if domain.dnssecdata is None %}
<div class="usa-alert usa-alert--info usa-alert--slim margin-bottom-3">
<div class="usa-alert__body">

View file

@ -4,6 +4,28 @@
{% block title %}DNS name servers | {{ domain.name }} | {% endblock %}
{% block domain_content %}
{% block breadcrumb %}
{% if portfolio %}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain' pk=domain.id %}" class="usa-breadcrumb__link"><span>{{ domain.name }}</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain-dns' pk=domain.id %}" class="usa-breadcrumb__link"><span>DNS</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>DNS name servers</span>
</li>
</ol>
</nav>
{% endif %}
{% endblock breadcrumb %}
{# this is right after the messages block in the parent template #}
{% for form in formset %}
{% include "includes/form_errors.html" with form=form %}

View file

@ -4,6 +4,25 @@
{% block title %}Security email | {{ domain.name }} | {% endblock %}
{% block domain_content %}
{% block breadcrumb %}
{% if portfolio %}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain' pk=domain.id %}" class="usa-breadcrumb__link"><span>{{ domain.name }}</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>Security email</span>
</li>
</ol>
</nav>
{% endif %}
{% endblock breadcrumb %}
{% include "includes/form_errors.html" with form=form %}
<h1>Security email</h1>

View file

@ -4,9 +4,30 @@
{% block title %}Suborganization{% if suborganization_name %} | suborganization_name{% endif %} | {% endblock %}
{% block domain_content %}
{% block breadcrumb %}
{% if portfolio %}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain' pk=domain.id %}" class="usa-breadcrumb__link"><span>{{ domain.name }}</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>Suborganization</span>
</li>
</ol>
</nav>
{% endif %}
{% endblock breadcrumb %}
{# this is right after the messages block in the parent template #}
{% include "includes/form_errors.html" with form=form %}
<h1>Suborganization</h1>
<p>

View file

@ -4,6 +4,25 @@
{% block title %}Domain managers | {{ domain.name }} | {% endblock %}
{% block domain_content %}
{% block breadcrumb %}
{% if portfolio %}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
</li>
<li class="usa-breadcrumb__list-item">
<a href="{% url 'domain' pk=domain.id %}" class="usa-breadcrumb__link"><span>{{ domain.name }}</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
<span>Domain managers</span>
</li>
</ol>
</nav>
{% endif %}
{% endblock breadcrumb %}
<h1>Domain managers</h1>
{% comment %}Copy below differs depending on whether view is in portfolio mode.{% endcomment %}

View file

@ -106,6 +106,26 @@
{% endfor %}
</ul>
{% endif %}
{% elif domain_permissions %}
{% if value.permissions.all %}
{% if value.permissions|length == 1 %}
<p class="margin-top-0">{{ value.permissions.0.user.email }} </p>
{% else %}
<ul class="usa-list usa-list--unstyled margin-top-0">
{% for item in value.permissions.all %}
<li>{{ item.user.email }}</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
{% if value.invitations.all %}
<h4 class="h4--sm-05">Invited domain managers</h4>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% for item in value.invitations.all %}
<li>{{ item.email }}</li>
{% endfor %}
</ul>
{% endif %}
{% else %}
<p class="margin-top-0 margin-bottom-0">
{% if value %}

View file

@ -824,6 +824,15 @@ class TestUser(TestCase):
cm.exception.message, "When portfolio roles or additional permissions are assigned, portfolio is required."
)
@less_console_noise_decorator
def test_user_with_admin_portfolio_role(self):
portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Hotel California")
self.assertFalse(self.user.is_portfolio_admin(portfolio))
UserPortfolioPermission.objects.get_or_create(
portfolio=portfolio, user=self.user, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
)
self.assertTrue(self.user.is_portfolio_admin(portfolio))
@less_console_noise_decorator
def test_get_active_requests_count_in_portfolio_returns_zero_if_no_portfolio(self):
# There is no portfolio referenced in session so should return 0

View file

@ -6,7 +6,7 @@ from django.urls import reverse
from django.contrib.auth import get_user_model
from waffle.testutils import override_flag
from api.tests.common import less_console_noise_decorator
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
from .common import MockEppLib, MockSESClient, create_user # type: ignore
from django_webtest import WebTest # type: ignore
import boto3_mocking # type: ignore
@ -142,6 +142,7 @@ class TestWithDomainPermissions(TestWithUser):
def tearDown(self):
try:
UserDomainRole.objects.all().delete()
DomainInvitation.objects.all().delete()
if hasattr(self.domain, "contacts"):
self.domain.contacts.all().delete()
DomainRequest.objects.all().delete()
@ -341,7 +342,7 @@ class TestDomainDetail(TestDomainOverview):
detail_page = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
self.assertNotContains(
detail_page, "To manage information for this domain, you must add yourself as a domain manager."
detail_page, "If you need to make updates, contact one of the listed domain managers."
)
@less_console_noise_decorator
@ -363,7 +364,12 @@ class TestDomainDetail(TestDomainOverview):
DomainInformation.objects.get_or_create(creator=user, domain=domain, portfolio=portfolio)
UserPortfolioPermission.objects.get_or_create(
user=user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
user=user,
portfolio=portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
additional_permissions=[
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS,
],
)
user.refresh_from_db()
self.client.force_login(user)
@ -377,6 +383,45 @@ class TestDomainDetail(TestDomainOverview):
)
# Check that user does not have option to Edit domain
self.assertNotContains(detail_page, "Edit")
# Check that invited domain manager section not displayed when no invited domain managers
self.assertNotContains(detail_page, "Invited domain managers")
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
def test_domain_readonly_on_detail_page_for_org_admin_not_manager(self):
"""Test that a domain, which is part of a portfolio, but for which the user is not a domain manager,
properly displays read only"""
portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test org", creator=self.user)
# need to create a different user than self.user because the user needs permission assignments
user = get_user_model().objects.create(
first_name="Test",
last_name="User",
email="bogus@example.gov",
phone="8003111234",
title="test title",
)
domain, _ = Domain.objects.get_or_create(name="bogusdomain.gov")
DomainInformation.objects.get_or_create(creator=user, domain=domain, portfolio=portfolio)
UserPortfolioPermission.objects.get_or_create(
user=user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
)
# add a domain invitation
DomainInvitation.objects.get_or_create(email="invited@example.com", domain=domain)
user.refresh_from_db()
self.client.force_login(user)
detail_page = self.client.get(f"/domain/{domain.id}")
# Check that alert message displays properly
self.assertContains(
detail_page,
"If you need to make updates, contact one of the listed domain managers.",
)
# Check that user does not have option to Edit domain
self.assertNotContains(detail_page, "Edit")
# Check that invited domain manager is displayed
self.assertContains(detail_page, "Invited domain managers")
self.assertContains(detail_page, "invited@example.com")
class TestDomainManagers(TestDomainOverview):