Skip to content

Commit

Permalink
Merge pull request #2842 from cisagov/za/2402-design-review
Browse files Browse the repository at this point in the history
#2402: Design Review for Analyst Portfolio View - [ZA] - MIGRATION
  • Loading branch information
zandercymatics authored Oct 4, 2024
2 parents de4f2fc + a219521 commit 14e8150
Show file tree
Hide file tree
Showing 39 changed files with 876 additions and 372 deletions.
2 changes: 1 addition & 1 deletion docs/operations/data_migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ Example: `cf ssh getgov-za`
| 1 | **emailTo** | Specifies where the email will be emailed. Defaults to [email protected] on production. |

## Populate federal agency initials and FCEB
This script adds to the "is_fceb" and "initials" fields on the FederalAgency model. This script expects a CSV of federal CIOs to pull from, which can be sourced from [here](https://docs.google.com/spreadsheets/d/14oXHFpKyUXS5_mDWARPusghGdHCrP67jCleOknaSx38/edit?gid=479328070#gid=479328070).
This script adds to the "is_fceb" and "acronym" fields on the FederalAgency model. This script expects a CSV of federal CIOs to pull from, which can be sourced from [here](https://docs.google.com/spreadsheets/d/14oXHFpKyUXS5_mDWARPusghGdHCrP67jCleOknaSx38/edit?gid=479328070#gid=479328070).

### Running on sandboxes

Expand Down
324 changes: 115 additions & 209 deletions src/registrar/admin.py

Large diffs are not rendered by default.

117 changes: 85 additions & 32 deletions src/registrar/assets/js/get-gov-admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -802,10 +802,15 @@ document.addEventListener('DOMContentLoaded', function() {
// $ symbolically denotes that this is using jQuery
let $federalAgency = django.jQuery("#id_federal_agency");
let organizationType = document.getElementById("id_organization_type");
if ($federalAgency && organizationType) {
let readonlyOrganizationType = document.querySelector(".field-organization_type .readonly");

let organizationNameContainer = document.querySelector(".field-organization_name");
let federalType = document.querySelector(".field-federal_type");

if ($federalAgency && (organizationType || readonlyOrganizationType)) {
// Attach the change event listener
$federalAgency.on("change", function() {
handleFederalAgencyChange($federalAgency, organizationType);
handleFederalAgencyChange($federalAgency, organizationType, readonlyOrganizationType, organizationNameContainer, federalType);
});
}

Expand All @@ -821,9 +826,33 @@ document.addEventListener('DOMContentLoaded', function() {
handleStateTerritoryChange(stateTerritory, urbanizationField);
});
}

// Handle hiding the organization name field when the organization_type is federal.
// Run this first one page load, then secondly on a change event.
handleOrganizationTypeChange(organizationType, organizationNameContainer, federalType);
organizationType.addEventListener("change", function() {
handleOrganizationTypeChange(organizationType, organizationNameContainer, federalType);
});
});

function handleFederalAgencyChange(federalAgency, organizationType) {
function handleOrganizationTypeChange(organizationType, organizationNameContainer, federalType) {
if (organizationType && organizationNameContainer) {
let selectedValue = organizationType.value;
if (selectedValue === "federal") {
hideElement(organizationNameContainer);
if (federalType) {
showElement(federalType);
}
} else {
showElement(organizationNameContainer);
if (federalType) {
hideElement(federalType);
}
}
}
}

function handleFederalAgencyChange(federalAgency, organizationType, readonlyOrganizationType, organizationNameContainer, federalType) {
// Don't do anything on page load
if (isInitialPageLoad) {
isInitialPageLoad = false;
Expand All @@ -838,27 +867,31 @@ document.addEventListener('DOMContentLoaded', function() {
return;
}

let organizationTypeValue = organizationType ? organizationType.value : readonlyOrganizationType.innerText.toLowerCase();
if (selectedText !== "Non-Federal Agency") {
if (organizationType.value !== "federal") {
organizationType.value = "federal";
if (organizationTypeValue !== "federal") {
if (organizationType){
organizationType.value = "federal";
}else {
readonlyOrganizationType.innerText = "Federal"
}
}
}else {
if (organizationType.value === "federal") {
organizationType.value = "";
if (organizationTypeValue === "federal") {
if (organizationType){
organizationType.value = "";
}else {
readonlyOrganizationType.innerText = "-"
}
}
}

// Get the associated senior official with this federal agency
let $seniorOfficial = django.jQuery("#id_senior_official");
if (!$seniorOfficial) {
console.log("Could not find the senior official field");
return;
}
handleOrganizationTypeChange(organizationType, organizationNameContainer, federalType);

// Determine if any changes are necessary to the display of portfolio type or federal type
// based on changes to the Federal Agency
let federalPortfolioApi = document.getElementById("federal_and_portfolio_types_from_agency_json_url").value;
fetch(`${federalPortfolioApi}?organization_type=${organizationType.value}&agency_name=${selectedText}`)
fetch(`${federalPortfolioApi}?&agency_name=${selectedText}`)
.then(response => {
const statusCode = response.status;
return response.json().then(data => ({ statusCode, data }));
Expand All @@ -869,14 +902,16 @@ document.addEventListener('DOMContentLoaded', function() {
return;
}
updateReadOnly(data.federal_type, '.field-federal_type');
updateReadOnly(data.portfolio_type, '.field-portfolio_type');
})
.catch(error => console.error("Error fetching federal and portfolio types: ", error));

// Hide the contactList initially.
// If we can update the contact information, it'll be shown again.
hideElement(contactList.parentElement);

let seniorOfficialAddUrl = document.getElementById("senior-official-add-url").value;
let $seniorOfficial = django.jQuery("#id_senior_official");
let readonlySeniorOfficial = document.querySelector(".field-senior_official .readonly");
let seniorOfficialApi = document.getElementById("senior_official_from_agency_json_url").value;
fetch(`${seniorOfficialApi}?agency_name=${selectedText}`)
.then(response => {
Expand All @@ -887,7 +922,12 @@ document.addEventListener('DOMContentLoaded', function() {
if (data.error) {
// Clear the field if the SO doesn't exist.
if (statusCode === 404) {
$seniorOfficial.val("").trigger("change");
if ($seniorOfficial && $seniorOfficial.length > 0) {
$seniorOfficial.val("").trigger("change");
}else {
// Show the "create one now" text if this field is none in readonly mode.
readonlySeniorOfficial.innerHTML = `<a href="${seniorOfficialAddUrl}">No senior official found. Create one now.</a>`;
}
console.warn("Record not found: " + data.error);
}else {
console.error("Error in AJAX call: " + data.error);
Expand All @@ -898,30 +938,43 @@ document.addEventListener('DOMContentLoaded', function() {
// Update the "contact details" blurb beneath senior official
updateContactInfo(data);
showElement(contactList.parentElement);


// Get the associated senior official with this federal agency
let seniorOfficialId = data.id;
let seniorOfficialName = [data.first_name, data.last_name].join(" ");
if (!seniorOfficialId || !seniorOfficialName || !seniorOfficialName.trim()){
// Clear the field if the SO doesn't exist
$seniorOfficial.val("").trigger("change");
return;
}

// Add the senior official to the dropdown.
// This format supports select2 - if we decide to convert this field in the future.
if ($seniorOfficial.find(`option[value='${seniorOfficialId}']`).length) {
// Select the value that is associated with the current Senior Official.
$seniorOfficial.val(seniorOfficialId).trigger("change");
} else {
// Create a DOM Option that matches the desired Senior Official. Then append it and select it.
let userOption = new Option(seniorOfficialName, seniorOfficialId, true, true);
$seniorOfficial.append(userOption).trigger("change");
if ($seniorOfficial && $seniorOfficial.length > 0) {
// If the senior official is a dropdown field, edit that
updateSeniorOfficialDropdown($seniorOfficial, seniorOfficialId, seniorOfficialName);
}else {
if (readonlySeniorOfficial) {
let seniorOfficialLink = `<a href=/admin/registrar/seniorofficial/${seniorOfficialId}/change/>${seniorOfficialName}</a>`
readonlySeniorOfficial.innerHTML = seniorOfficialName ? seniorOfficialLink : "-";
}
}
})
.catch(error => console.error("Error fetching senior official: ", error));

}

function updateSeniorOfficialDropdown(dropdown, seniorOfficialId, seniorOfficialName) {
if (!seniorOfficialId || !seniorOfficialName || !seniorOfficialName.trim()){
// Clear the field if the SO doesn't exist
dropdown.val("").trigger("change");
return;
}

// Add the senior official to the dropdown.
// This format supports select2 - if we decide to convert this field in the future.
if (dropdown.find(`option[value='${seniorOfficialId}']`).length) {
// Select the value that is associated with the current Senior Official.
dropdown.val(seniorOfficialId).trigger("change");
} else {
// Create a DOM Option that matches the desired Senior Official. Then append it and select it.
let userOption = new Option(seniorOfficialName, seniorOfficialId, true, true);
dropdown.append(userOption).trigger("change");
}
}

function handleStateTerritoryChange(stateTerritory, urbanizationField) {
let selectedValue = stateTerritory.value;
if (selectedValue === "PR") {
Expand Down
10 changes: 8 additions & 2 deletions src/registrar/assets/sass/_theme/_admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,8 @@ details.dja-detail-table {
background-color: var(--body-bg);
.dja-details-summary {
cursor: pointer;
color: var(--body-quiet-color);
color: var(--link-fg);
text-decoration: underline;
}

@media (max-width: 1024px){
Expand Down Expand Up @@ -922,4 +923,9 @@ ul.add-list-reset {
overflow: visible;
word-break: break-all;
max-width: 100%;
}
}

.organization-admin-label {
font-weight: 600;
font-size: .8125rem;
}
2 changes: 2 additions & 0 deletions src/registrar/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,8 @@ class JsonServerFormatter(ServerFormatter):

def format(self, record):
formatted_record = super().format(record)
if not hasattr(record, "server_time"):
record.server_time = self.formatTime(record, self.datefmt)
log_entry = {"server_time": record.server_time, "level": record.levelname, "message": formatted_record}
return json.dumps(log_entry)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ def handle(self, federal_cio_csv_path, **kwargs):
self.federal_agency_dict[agency_name.strip()] = (initials, agency_status)

# Update every federal agency record
self.mass_update_records(FederalAgency, {"agency__isnull": False}, ["initials", "is_fceb"])
self.mass_update_records(FederalAgency, {"agency__isnull": False}, ["acronym", "is_fceb"])

def update_record(self, record: FederalAgency):
"""For each record, update the initials and is_fceb field if data exists for it"""
initials, agency_status = self.federal_agency_dict.get(record.agency)

record.initials = initials
record.acronym = initials
if agency_status and isinstance(agency_status, str) and agency_status.strip().upper() == "FCEB":
record.is_fceb = True
else:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Generated by Django 4.2.10 on 2024-09-30 17:59

from django.db import migrations, models
import django.db.models.deletion
import registrar.models.federal_agency


class Migration(migrations.Migration):

dependencies = [
("registrar", "0129_alter_portfolioinvitation_portfolio_roles_and_more"),
]

operations = [
migrations.RenameField(
model_name="federalagency",
old_name="initials",
new_name="acronym",
),
migrations.AlterField(
model_name="federalagency",
name="acronym",
field=models.CharField(
blank=True,
help_text="Acronym commonly used to reference the federal agency (Optional)",
max_length=10,
null=True,
),
),
migrations.AlterField(
model_name="domaininformation",
name="portfolio",
field=models.ForeignKey(
blank=True,
help_text="If blank, domain is not associated with a portfolio.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="information_portfolio",
to="registrar.portfolio",
),
),
migrations.AlterField(
model_name="domaininformation",
name="sub_organization",
field=models.ForeignKey(
blank=True,
help_text="If blank, domain is associated with the overarching organization for this portfolio.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="information_sub_organization",
to="registrar.suborganization",
verbose_name="Suborganization",
),
),
migrations.AlterField(
model_name="domainrequest",
name="portfolio",
field=models.ForeignKey(
blank=True,
help_text="If blank, request is not associated with a portfolio.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="DomainRequest_portfolio",
to="registrar.portfolio",
),
),
migrations.AlterField(
model_name="domainrequest",
name="sub_organization",
field=models.ForeignKey(
blank=True,
help_text="If blank, request is associated with the overarching organization for this portfolio.",
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="request_sub_organization",
to="registrar.suborganization",
verbose_name="Suborganization",
),
),
migrations.AlterField(
model_name="federalagency",
name="federal_type",
field=models.CharField(
blank=True,
choices=[("executive", "Executive"), ("judicial", "Judicial"), ("legislative", "Legislative")],
max_length=20,
null=True,
),
),
migrations.AlterField(
model_name="federalagency",
name="is_fceb",
field=models.BooleanField(
blank=True, help_text="Federal Civilian Executive Branch (FCEB)", null=True, verbose_name="FCEB"
),
),
migrations.AlterField(
model_name="portfolio",
name="federal_agency",
field=models.ForeignKey(
default=registrar.models.federal_agency.FederalAgency.get_non_federal_agency,
on_delete=django.db.models.deletion.PROTECT,
to="registrar.federalagency",
),
),
migrations.AlterField(
model_name="portfolio",
name="organization_name",
field=models.CharField(blank=True, null=True),
),
migrations.AlterField(
model_name="portfolio",
name="organization_type",
field=models.CharField(
blank=True,
choices=[
("federal", "Federal"),
("interstate", "Interstate"),
("state_or_territory", "State or territory"),
("tribal", "Tribal"),
("county", "County"),
("city", "City"),
("special_district", "Special district"),
("school_district", "School district"),
],
max_length=255,
null=True,
),
),
migrations.AlterField(
model_name="portfolio",
name="senior_official",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="portfolios",
to="registrar.seniorofficial",
),
),
migrations.AlterField(
model_name="suborganization",
name="name",
field=models.CharField(max_length=1000, unique=True, verbose_name="Suborganization"),
),
]
Loading

0 comments on commit 14e8150

Please sign in to comment.