Skip to content

Commit

Permalink
Merge branch 'main' into za/2976-senior-official-federal-agency-changes
Browse files Browse the repository at this point in the history
  • Loading branch information
zandercymatics committed Nov 1, 2024
2 parents f028e16 + 835c0ae commit 48c3b4c
Show file tree
Hide file tree
Showing 26 changed files with 1,572 additions and 140 deletions.
17 changes: 13 additions & 4 deletions .github/workflows/clone-staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ jobs:
CF_PASSWORD: CF_MS_PASSWORD
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
cache: 'pip' # caching pip dependencies

- name: Install CG Tool
run: pip install git+https://github.com/cloud-gov/cg-manage-rds.git

- name: Share DB Service
uses: cloud-gov/cg-cli-tools@main
Expand All @@ -36,7 +45,7 @@ jobs:
- name: Clone Database
uses: cloud-gov/cg-cli-tools@main
with:
cf_username: ${{ secrets.CF_MS_USERNAM }}
cf_username: ${{ secrets.CF_MS_USERNAME }}
cf_password: ${{ secrets.CF_MS_PASSWORD }}
cf_org: cisa-dotgov
cf_space: ${{ env.SOURCE_ENVIRONMENT }}
Expand All @@ -45,8 +54,8 @@ jobs:
- name: Unshare DB Service
uses: cloud-gov/cg-cli-tools@main
with:
cf_username: ${{ secrets.CF_MS_USERNAM }}
cf_username: ${{ secrets.CF_MS_USERNAME }}
cf_password: ${{ secrets.CF_MS_PASSWORD }}
cf_org: cisa-dotgov
cf_space: ${{ env.SOURCE_ENVIRONMENT }}
cf_command: unshare-service getgov-${{ env.DESTINATION_ENVIRONMENT }}-database -s ${{ env.SOURCE_ENVIRONMENT }}
cf_command: unshare-service getgov-${{ env.DESTINATION_ENVIRONMENT }}-database -s ${{ env.SOURCE_ENVIRONMENT }}
5 changes: 4 additions & 1 deletion src/.pa11yci
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
"http://localhost:8080/request/anything_else/",
"http://localhost:8080/request/requirements/",
"http://localhost:8080/request/finished/",
"http://localhost:8080/user-profile/"
"http://localhost:8080/request/requesting_entity/",
"http://localhost:8080/user-profile/",
"http://localhost:8080/members/",
"http://localhost:8080/members/new-member"
]
}
39 changes: 38 additions & 1 deletion src/registrar/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial
from registrar.utility.constants import BranchChoices
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
from registrar.utility.waffle import flag_is_active_for_user
from registrar.views.utility.mixins import OrderableFieldsMixin
from django.contrib.admin.views.main import ORDER_VAR
from registrar.widgets import NoAutocompleteFilteredSelectMultiple
Expand Down Expand Up @@ -1479,7 +1480,18 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
search_help_text = "Search by domain."

fieldsets = [
(None, {"fields": ["portfolio", "sub_organization", "creator", "domain_request", "notes"]}),
(
None,
{
"fields": [
"portfolio",
"sub_organization",
"creator",
"domain_request",
"notes",
]
},
),
(".gov domain", {"fields": ["domain"]}),
("Contacts", {"fields": ["senior_official", "other_contacts", "no_other_contacts_rationale"]}),
("Background info", {"fields": ["anything_else"]}),
Expand Down Expand Up @@ -1749,6 +1761,9 @@ def status_history(self, obj):
"fields": [
"portfolio",
"sub_organization",
"requested_suborganization",
"suborganization_city",
"suborganization_state_territory",
"status_history",
"status",
"rejection_reason",
Expand Down Expand Up @@ -1850,6 +1865,9 @@ def status_history(self, obj):
"cisa_representative_first_name",
"cisa_representative_last_name",
"cisa_representative_email",
"requested_suborganization",
"suborganization_city",
"suborganization_state_territory",
]
autocomplete_fields = [
"approved_domain",
Expand All @@ -1869,6 +1887,25 @@ def status_history(self, obj):

change_form_template = "django/admin/domain_request_change_form.html"

def get_fieldsets(self, request, obj=None):
fieldsets = super().get_fieldsets(request, obj)

# Hide certain suborg fields behind the organization feature flag
# if it is not enabled
if not flag_is_active_for_user(request.user, "organization_feature"):
excluded_fields = [
"requested_suborganization",
"suborganization_city",
"suborganization_state_territory",
]
modified_fieldsets = []
for name, data in fieldsets:
fields = data.get("fields", [])
fields = tuple(field for field in fields if field not in excluded_fields)
modified_fieldsets.append((name, {**data, "fields": fields}))
return modified_fieldsets
return fieldsets

# Trigger action when a fieldset is changed
def save_model(self, request, obj, form, change):
"""Custom save_model definition that handles edge cases"""
Expand Down
64 changes: 64 additions & 0 deletions src/registrar/assets/js/get-gov-admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,49 @@ function addOrRemoveSessionBoolean(name, add){

// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
// Event handlers.
/** Helper function that handles business logic for the suborganization field.
* Can be used anywhere the suborganization dropdown exists
*/
function handleSuborganizationFields(
portfolioDropdownSelector="#id_portfolio",
suborgDropdownSelector="#id_sub_organization",
requestedSuborgFieldSelector=".field-requested_suborganization",
suborgCitySelector=".field-suborganization_city",
suborgStateTerritorySelector=".field-suborganization_state_territory"
) {
// These dropdown are select2 fields so they must be interacted with via jquery
const portfolioDropdown = django.jQuery(portfolioDropdownSelector)
const suborganizationDropdown = django.jQuery(suborgDropdownSelector)
const requestedSuborgField = document.querySelector(requestedSuborgFieldSelector);
const suborgCity = document.querySelector(suborgCitySelector);
const suborgStateTerritory = document.querySelector(suborgStateTerritorySelector);
if (!suborganizationDropdown || !requestedSuborgField || !suborgCity || !suborgStateTerritory) {
console.error("Requested suborg fields not found.");
return;
}

function toggleSuborganizationFields() {
if (portfolioDropdown.val() && !suborganizationDropdown.val()) {
showElement(requestedSuborgField);
showElement(suborgCity);
showElement(suborgStateTerritory);
}else {
hideElement(requestedSuborgField);
hideElement(suborgCity);
hideElement(suborgStateTerritory);
}
}

// Run the function once on page startup, then attach an event listener
toggleSuborganizationFields();
suborganizationDropdown.on("change", toggleSuborganizationFields);
portfolioDropdown.on("change", toggleSuborganizationFields);
}

// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
// Initialization code.


/** An IIFE for pages in DjangoAdmin that use modals.
* Dja strips out form elements, and modals generate their content outside
* of the current form scope, so we need to "inject" these inputs.
Expand Down Expand Up @@ -1173,3 +1212,28 @@ document.addEventListener('DOMContentLoaded', function() {
};
}
})();

/** An IIFE for dynamic DomainRequest fields
*/
(function dynamicDomainRequestFields(){
const domainRequestPage = document.getElementById("domainrequest_form");
if (domainRequestPage) {
handleSuborganizationFields();
}
})();


/** An IIFE for dynamic DomainInformation fields
*/
(function dynamicDomainInformationFields(){
const domainInformationPage = document.getElementById("domaininformation_form");
// DomainInformation is embedded inside domain so this should fire there too
const domainPage = document.getElementById("domain_form");
if (domainInformationPage) {
handleSuborganizationFields();
}

if (domainPage) {
handleSuborganizationFields(portfolioDropdownSelector="#id_domain_info-0-portfolio", suborgDropdownSelector="#id_domain_info-0-sub_organization");
}
})();
120 changes: 103 additions & 17 deletions src/registrar/assets/js/get-gov.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const hideElement = (element) => {
};

/**
* Show element
*
* Show element
*
*/
const showElement = (element) => {
element.classList.remove('display-none');
Expand Down Expand Up @@ -297,28 +297,56 @@ function clearValidators(el) {
* radio button is false (hides this element if true)
* **/
function HookupYesNoListener(radioButtonName, elementIdToShowIfYes, elementIdToShowIfNo) {
HookupRadioTogglerListener(radioButtonName, {
'True': elementIdToShowIfYes,
'False': elementIdToShowIfNo
});
}

/**
* Hookup listeners for radio togglers in form fields.
*
* Parameters:
* - radioButtonName: The "name=" value for the radio buttons being used as togglers
* - valueToElementMap: An object where keys are the values of the radio buttons,
* and values are the corresponding DOM element IDs to show. All other elements will be hidden.
*
* Usage Example:
* Assuming you have radio buttons with values 'option1', 'option2', and 'option3',
* and corresponding DOM IDs 'section1', 'section2', 'section3'.
*
* HookupValueBasedListener('exampleRadioGroup', {
* 'option1': 'section1',
* 'option2': 'section2',
* 'option3': 'section3'
* });
**/
function HookupRadioTogglerListener(radioButtonName, valueToElementMap) {
// Get the radio buttons
let radioButtons = document.querySelectorAll('input[name="'+radioButtonName+'"]');

// Extract the list of all element IDs from the valueToElementMap
let allElementIds = Object.values(valueToElementMap);

function handleRadioButtonChange() {
// Check the value of the selected radio button
// Attempt to find the radio button element that is checked
// Find the checked radio button
let radioButtonChecked = document.querySelector('input[name="'+radioButtonName+'"]:checked');

// Check if the element exists before accessing its value
let selectedValue = radioButtonChecked ? radioButtonChecked.value : null;

switch (selectedValue) {
case 'True':
toggleTwoDomElements(elementIdToShowIfYes, elementIdToShowIfNo, 1);
break;

case 'False':
toggleTwoDomElements(elementIdToShowIfYes, elementIdToShowIfNo, 2);
break;
// Hide all elements by default
allElementIds.forEach(function (elementId) {
let element = document.getElementById(elementId);
if (element) {
hideElement(element);
}
});

default:
toggleTwoDomElements(elementIdToShowIfYes, elementIdToShowIfNo, 0);
// Show the relevant element for the selected value
if (selectedValue && valueToElementMap[selectedValue]) {
let elementToShow = document.getElementById(valueToElementMap[selectedValue]);
if (elementToShow) {
showElement(elementToShow);
}
}
}

Expand All @@ -328,11 +356,12 @@ function HookupYesNoListener(radioButtonName, elementIdToShowIfYes, elementIdToS
radioButton.addEventListener('change', handleRadioButtonChange);
});

// initialize
// Initialize by checking the current state
handleRadioButtonChange();
}
}


// A generic display none/block toggle function that takes an integer param to indicate how the elements toggle
function toggleTwoDomElements(ele1, ele2, index) {
let element1 = document.getElementById(ele1);
Expand Down Expand Up @@ -912,6 +941,18 @@ function setupUrbanizationToggle(stateTerritoryField) {
HookupYesNoListener("additional_details-has_anything_else_text",'anything-else', null)
})();


/**
* An IIFE that listens to the yes/no radio buttons on the anything else form and toggles form field visibility accordingly
*
*/
(function newMemberFormListener() {
HookupRadioTogglerListener('member_access_level', {
'admin': 'new-member-admin-permissions',
'basic': 'new-member-basic-permissions'
});
})();

/**
* An IIFE that disables the delete buttons on nameserver forms on page load if < 3 forms
*
Expand Down Expand Up @@ -2734,3 +2775,48 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
})();

/** An IIFE that intializes the requesting entity page.
* This page has a radio button that dynamically toggles some fields
* Within that, the dropdown also toggles some additional form elements.
*/
(function handleRequestingEntityFieldset() {
// Sadly, these ugly ids are the auto generated with this prefix
const formPrefix = "portfolio_requesting_entity"
const radioFieldset = document.getElementById(`id_${formPrefix}-requesting_entity_is_suborganization__fieldset`);
const radios = radioFieldset?.querySelectorAll(`input[name="${formPrefix}-requesting_entity_is_suborganization"]`);
const select = document.getElementById(`id_${formPrefix}-sub_organization`);
const suborgContainer = document.getElementById("suborganization-container");
const suborgDetailsContainer = document.getElementById("suborganization-container__details");
if (!radios || !select || !suborgContainer || !suborgDetailsContainer) return;

// requestingSuborganization: This just broadly determines if they're requesting a suborg at all
// requestingNewSuborganization: This variable determines if the user is trying to *create* a new suborganization or not.
var requestingSuborganization = Array.from(radios).find(radio => radio.checked)?.value === "True";
var requestingNewSuborganization = document.getElementById(`id_${formPrefix}-is_requesting_new_suborganization`);

function toggleSuborganization(radio=null) {
if (radio != null) requestingSuborganization = radio?.checked && radio.value === "True";
requestingSuborganization ? showElement(suborgContainer) : hideElement(suborgContainer);
requestingNewSuborganization.value = requestingSuborganization && select.value === "other" ? "True" : "False";
requestingNewSuborganization.value === "True" ? showElement(suborgDetailsContainer) : hideElement(suborgDetailsContainer);
}

// Add fake "other" option to sub_organization select
if (select && !Array.from(select.options).some(option => option.value === "other")) {
select.add(new Option("Other (enter your organization manually)", "other"));
}

if (requestingNewSuborganization.value === "True") {
select.value = "other";
}

// Add event listener to is_suborganization radio buttons, and run for initial display
toggleSuborganization();
radios.forEach(radio => {
radio.addEventListener("click", () => toggleSuborganization(radio));
});

// Add event listener to the suborg dropdown to show/hide the suborg details section
select.addEventListener("change", () => toggleSuborganization());
})();
5 changes: 5 additions & 0 deletions src/registrar/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@
# views.PortfolioNoMembersView.as_view(),
# name="no-portfolio-members",
# ),
path(
"members/new-member/",
views.NewMemberView.as_view(),
name="new-member",
),
path(
"requests/",
views.PortfolioDomainRequestsView.as_view(),
Expand Down
Loading

0 comments on commit 48c3b4c

Please sign in to comment.