Merge branch 'main' into meoward/3228-domain-lists

This commit is contained in:
David Kennedy 2025-01-08 15:09:20 -05:00
commit bf3c97a310
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
10 changed files with 960 additions and 3804 deletions

4666
src/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.26.0", "@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0", "@babel/preset-env": "^7.26.0",
"@uswds/compile": "1.1.0", "@uswds/compile": "1.2.1",
"babel-loader": "^9.2.1", "babel-loader": "^9.2.1",
"sass-loader": "^12.6.0", "sass-loader": "^12.6.0",
"webpack": "^5.96.1", "webpack": "^5.96.1",

View file

@ -13,6 +13,7 @@ export function handleRequestingEntityFieldset() {
const selectParent = select?.parentElement; const selectParent = select?.parentElement;
const suborgContainer = document.getElementById("suborganization-container"); const suborgContainer = document.getElementById("suborganization-container");
const suborgDetailsContainer = document.getElementById("suborganization-container__details"); const suborgDetailsContainer = document.getElementById("suborganization-container__details");
const suborgAddtlInstruction = document.getElementById("suborganization-addtl-instruction");
const subOrgCreateNewOption = document.getElementById("option-to-add-suborg")?.value; const subOrgCreateNewOption = document.getElementById("option-to-add-suborg")?.value;
// Make sure all crucial page elements exist before proceeding. // Make sure all crucial page elements exist before proceeding.
// This more or less ensures that we are on the Requesting Entity page, and not elsewhere. // This more or less ensures that we are on the Requesting Entity page, and not elsewhere.
@ -26,7 +27,13 @@ export function handleRequestingEntityFieldset() {
function toggleSuborganization(radio=null) { function toggleSuborganization(radio=null) {
if (radio != null) requestingSuborganization = radio?.checked && radio.value === "True"; if (radio != null) requestingSuborganization = radio?.checked && radio.value === "True";
requestingSuborganization ? showElement(suborgContainer) : hideElement(suborgContainer); requestingSuborganization ? showElement(suborgContainer) : hideElement(suborgContainer);
requestingNewSuborganization.value = requestingSuborganization && select.value === "other" ? "True" : "False"; if (select.options.length == 2) { // --Select-- and other are the only options
hideElement(selectParent); // Hide the select drop down and indicate requesting new suborg
hideElement(suborgAddtlInstruction); // Hide additional instruction related to the list
requestingNewSuborganization.value = "True";
} else {
requestingNewSuborganization.value = requestingSuborganization && select.value === "other" ? "True" : "False";
}
requestingNewSuborganization.value === "True" ? showElement(suborgDetailsContainer) : hideElement(suborgDetailsContainer); requestingNewSuborganization.value === "True" ? showElement(suborgDetailsContainer) : hideElement(suborgDetailsContainer);
} }

View file

@ -137,7 +137,7 @@ export class MembersTable extends BaseTable {
} }
// This easter egg is only for fixtures that dont have names as we are displaying their emails // This easter egg is only for fixtures that dont have names as we are displaying their emails
// All prod users will have emails linked to their account // All prod users will have emails linked to their account
if (customTableOptions.needsAdditionalColumn) MembersTable.addMemberModal(num_domains, member.email || "Samwise Gamgee", member_delete_url, unique_id, row); if (customTableOptions.needsAdditionalColumn) MembersTable.addMemberDeleteModal(num_domains, member.email || "Samwise Gamgee", member_delete_url, unique_id, row);
} }
/** /**
@ -417,24 +417,21 @@ export class MembersTable extends BaseTable {
* @param {string} submit_delete_url - `${member_type}-${member_id}/delete` * @param {string} submit_delete_url - `${member_type}-${member_id}/delete`
* @param {HTMLElement} wrapper_element - The element to which the modal is appended * @param {HTMLElement} wrapper_element - The element to which the modal is appended
*/ */
static addMemberModal(num_domains, member_email, submit_delete_url, id, wrapper_element) { static addMemberDeleteModal(num_domains, member_email, submit_delete_url, id, wrapper_element) {
let modalHeading = '';
let modalDescription = '';
if (num_domains == 0){ let modalHeading = ``;
let modalDescription = ``;
if (num_domains >= 0){
modalHeading = `Are you sure you want to delete ${member_email}?`; modalHeading = `Are you sure you want to delete ${member_email}?`;
modalDescription = `They will no longer be able to access this organization. modalDescription = `They will no longer be able to access this organization.
This action cannot be undone.`; This action cannot be undone.`;
} else if (num_domains == 1) { if (num_domains >= 1)
modalHeading = `Are you sure you want to delete ${member_email}?`; {
modalDescription = `<b>${member_email}</b> currently manages ${num_domains} domain in the organization. modalDescription = `<b>${member_email}</b> currently manages ${num_domains} domain${num_domains > 1 ? "s": ""} in the organization.
Removing them from the organization will remove all of their domains. They will no longer be able to Removing them from the organization will remove them from all of their domains. They will no longer be able to
access this organization. This action cannot be undone.`; access this organization. This action cannot be undone.`;
} else if (num_domains > 1) { }
modalHeading = `Are you sure you want to delete ${member_email}?`;
modalDescription = `<b>${member_email}</b> currently manages ${num_domains} domains in the organization.
Removing them from the organization will remove all of their domains. They will no longer be able to
access this organization. This action cannot be undone.`;
} }
const modalSubmit = ` const modalSubmit = `

View file

@ -1,4 +1,4 @@
from datetime import timedelta from datetime import datetime, timedelta
from django.utils import timezone from django.utils import timezone
import logging import logging
import random import random
@ -126,7 +126,22 @@ class DomainRequestFixture:
# TODO for a future ticket: Allow for more than just "federal" here # TODO for a future ticket: Allow for more than just "federal" here
request.generic_org_type = request_dict["generic_org_type"] if "generic_org_type" in request_dict else "federal" request.generic_org_type = request_dict["generic_org_type"] if "generic_org_type" in request_dict else "federal"
if request.status != "started": if request.status != "started":
request.last_submitted_date = fake.date() # Generate fake data for first_submitted_date and last_submitted_date
# First generate a random date set to be later than 2020 (or something)
# (if we just use fake.date() we might get years like 1970 or earlier)
earliest_date_allowed = datetime(2020, 1, 1).date()
end_date = datetime.today().date() # Today's date (latest allowed date)
days_range = (end_date - earliest_date_allowed).days
first_submitted_date = earliest_date_allowed + timedelta(days=random.randint(0, days_range)) # nosec
# Generate a random positive offset to ensure last_submitted_date is later
# (Start with 1 to ensure at least 1 day difference)
offset_days = random.randint(1, 30) # nosec
last_submitted_date = first_submitted_date + timedelta(days=offset_days)
# Convert back to strings before assigning
request.first_submitted_date = first_submitted_date.strftime("%Y-%m-%d")
request.last_submitted_date = last_submitted_date.strftime("%Y-%m-%d")
request.federal_type = ( request.federal_type = (
request_dict["federal_type"] request_dict["federal_type"]
if "federal_type" in request_dict if "federal_type" in request_dict

View file

@ -159,9 +159,12 @@ class RequestingEntityYesNoForm(BaseYesNoForm):
"""Extend the initialization of the form from RegistrarForm __init__""" """Extend the initialization of the form from RegistrarForm __init__"""
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.domain_request.portfolio: if self.domain_request.portfolio:
choose_text = (
"(choose from list)" if self.domain_request.portfolio.portfolio_suborganizations.exists() else ""
)
self.form_choices = ( self.form_choices = (
(False, self.domain_request.portfolio), (False, self.domain_request.portfolio),
(True, "A suborganization (choose from list)"), (True, f"A suborganization {choose_text}"),
) )
self.fields[self.field_name] = self.get_typed_choice_field() self.fields[self.field_name] = self.get_typed_choice_field()

View file

@ -38,8 +38,9 @@
<div id="suborganization-container" class="margin-top-4"> <div id="suborganization-container" class="margin-top-4">
<h2>Add suborganization information</h2> <h2>Add suborganization information</h2>
<p> <p>
This information will be published in <a class="usa-link usa-link--always-blue" target="_blank" href="{% public_site_url 'about/data' %}">.govs public data</a>. If you dont see your suborganization in the list, This information will be published in <a class="usa-link usa-link--always-blue" target="_blank" href="{% public_site_url 'about/data' %}">.govs public data</a>.
select “other.” <span id="suborganization-addtl-instruction"> If you dont see your suborganization in the list,
select “other.”</span>
</p> </p>
{% with attr_required=True %} {% with attr_required=True %}
{% input_with_errors forms.1.sub_organization %} {% input_with_errors forms.1.sub_organization %}

View file

@ -338,7 +338,7 @@ class GetRequestsJsonTest(TestWithUser, WebTest):
self.assertEqual(expected_domain_request.creator.email, creator[i]) self.assertEqual(expected_domain_request.creator.email, creator[i])
# Check action url, action label and svg icon # Check action url, action label and svg icon
# Example domain requests will test each of below three scenarios # Example domain requests will test each of below three scenarios
if creator[i] != self.user.email: if creator[i] != self.user.email or not self.user.has_edit_request_portfolio_permission(self.portfolio):
# Test case where action is View # Test case where action is View
self.assertEqual("View", action_labels[i]) self.assertEqual("View", action_labels[i])
self.assertEqual( self.assertEqual(

View file

@ -1952,7 +1952,7 @@ class DomainRequestGrowth(DomainRequestExport):
"Domain request", "Domain request",
"Domain type", "Domain type",
"Federal type", "Federal type",
"Submitted at", "First submitted date",
] ]
@classmethod @classmethod
@ -1976,7 +1976,6 @@ class DomainRequestGrowth(DomainRequestExport):
start_date_formatted = format_start_date(start_date) start_date_formatted = format_start_date(start_date)
end_date_formatted = format_end_date(end_date) end_date_formatted = format_end_date(end_date)
return Q( return Q(
status=DomainRequest.DomainRequestStatus.SUBMITTED,
last_submitted_date__lte=end_date_formatted, last_submitted_date__lte=end_date_formatted,
last_submitted_date__gte=start_date_formatted, last_submitted_date__gte=start_date_formatted,
) )

View file

@ -125,15 +125,6 @@ def serialize_domain_request(request, domain_request, user):
DomainRequest.DomainRequestStatus.WITHDRAWN, DomainRequest.DomainRequestStatus.WITHDRAWN,
] ]
# Determine if the request is deletable
if not user.is_org_user(request):
is_deletable = domain_request.status in deletable_statuses
else:
portfolio = request.session.get("portfolio")
is_deletable = (
domain_request.status in deletable_statuses and user.has_edit_request_portfolio_permission(portfolio)
) and domain_request.creator == user
# Determine action label based on user permissions and request status # Determine action label based on user permissions and request status
editable_statuses = [ editable_statuses = [
DomainRequest.DomainRequestStatus.STARTED, DomainRequest.DomainRequestStatus.STARTED,
@ -141,11 +132,26 @@ def serialize_domain_request(request, domain_request, user):
DomainRequest.DomainRequestStatus.WITHDRAWN, DomainRequest.DomainRequestStatus.WITHDRAWN,
] ]
if user.has_edit_request_portfolio_permission and domain_request.creator == user: # No portfolio action_label
if domain_request.creator == user:
action_label = "Edit" if domain_request.status in editable_statuses else "Manage" action_label = "Edit" if domain_request.status in editable_statuses else "Manage"
else: else:
action_label = "View" action_label = "View"
# No portfolio deletable
is_deletable = domain_request.status in deletable_statuses
# If we're working with a portfolio
if user.is_org_user(request):
portfolio = request.session.get("portfolio")
is_deletable = (
domain_request.status in deletable_statuses and user.has_edit_request_portfolio_permission(portfolio)
) and domain_request.creator == user
if user.has_edit_request_portfolio_permission(portfolio) and domain_request.creator == user:
action_label = "Edit" if domain_request.status in editable_statuses else "Manage"
else:
action_label = "View"
# Map the action label to corresponding URLs and icons # Map the action label to corresponding URLs and icons
action_url_map = { action_url_map = {
"Edit": reverse("edit-domain-request", kwargs={"id": domain_request.id}), "Edit": reverse("edit-domain-request", kwargs={"id": domain_request.id}),