Skip to content

Commit

Permalink
code refactors for readability and efficiency, comments for clarity
Browse files Browse the repository at this point in the history
  • Loading branch information
dave-kennedy-ecs committed Oct 23, 2024
1 parent b06ffec commit 07e4960
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 80 deletions.
225 changes: 149 additions & 76 deletions src/registrar/assets/js/get-gov.js
Original file line number Diff line number Diff line change
Expand Up @@ -1874,7 +1874,24 @@ class MembersTable extends LoadTableBase {
super('.members__table', '.members__table-wrapper', '#members__search-field', '#members__search-field-submit', '.members__reset-search', '.members__reset-filters', '.members__no-data', '.members__no-search-results');
}

/**
* Initializes "Show More" buttons on the page, enabling toggle functionality to show or hide content.
*
* The function finds elements with "Show More" buttons and sets up a click event listener to toggle the visibility
* of a corresponding content div. When clicked, the button updates its visual state (e.g., text/icon change),
* and the associated content is shown or hidden based on its current visibility status.
*
* @function initShowMoreButtons
*/
initShowMoreButtons() {
/**
* Toggles the visibility of a content section when the "Show More" button is clicked.
* Updates the button text/icon based on whether the content is shown or hidden.
*
* @param {HTMLElement} toggleButton - The button that toggles the content visibility.
* @param {HTMLElement} contentDiv - The content div whose visibility is toggled.
* @param {HTMLElement} buttonParentRow - The parent row element containing the button.
*/
function toggleShowMoreButton(toggleButton, contentDiv, buttonParentRow) {
const spanElement = toggleButton.querySelector('span');
const useElement = toggleButton.querySelector('use');
Expand All @@ -1896,6 +1913,7 @@ class MembersTable extends LoadTableBase {
let toggleButtons = document.querySelectorAll('.usa-button--show-more-button');
toggleButtons.forEach((toggleButton) => {

// get contentDiv for element specified in data-for attribute of toggleButton
let dataFor = toggleButton.dataset.for;
let contentDiv = document.getElementById(dataFor);
let buttonParentRow = toggleButton.parentElement.parentElement;
Expand All @@ -1911,50 +1929,141 @@ class MembersTable extends LoadTableBase {
}

/**
* Helper function which takes last_active and returns display value and sort value
* @param {*} last_active - UTC date, or strings "Invited" or "Invalid date"
* @returns
* Converts a given `last_active` value into a display value and a numeric sort value.
* The input can be a UTC date, the strings "Invited", "Invalid date", or null/undefined.
*
* @param {string} last_active - UTC date string or special status like "Invited" or "Invalid date".
* @returns {Object} - An object containing `display_value` (formatted date or status string)
* and `sort_value` (numeric value for sorting).
*/
handleLastActive(last_active) {
let last_active_display = '';
let last_active_sort_value = -1; // sort by default as numeric value to sort with dates

const invited = 'Invited';
const invalid_date = 'Invalid date';
const options = { year: 'numeric', month: 'long', day: 'numeric' }; // Format options for date

// Check if last_active is not null
if (last_active) {
if (last_active === invited) {
last_active_display = invited;
last_active_sort_value = 0; // sort as numeric value
} else if (last_active === invalid_date) {
last_active_display = invalid_date;
const options = { year: 'numeric', month: 'long', day: 'numeric' }; // Date display format

let display_value = invalid_date; // Default display value for invalid or null dates
let sort_value = -1; // Default sort value for invalid or null dates

if (last_active === invited) {
// Handle "Invited" status: special case with 0 sort value
display_value = invited;
sort_value = 0;
} else if (last_active && last_active !== invalid_date) {
// Parse and format valid UTC date strings
const parsedDate = new Date(last_active);

if (!isNaN(parsedDate.getTime())) {
// Valid date
display_value = parsedDate.toLocaleDateString('en-US', options);
sort_value = parsedDate.getTime(); // Use timestamp for sorting
} else {
const parsedDate = new Date(last_active);

try {
if (!isNaN(parsedDate.getTime())) { // Check if the date is valid
last_active_display = parsedDate.toLocaleDateString('en-US', options);
last_active_sort_value = parsedDate.getTime(); // sort as numeric value, seconds since 1970
} else {
throw new Error(invalid_date); // Throw an error to catch in catch block
}
} catch (e) { // catch invalid values and treat as 'Invalid date'
console.error(`Error parsing date: ${last_active}. Error: ${e}`);
last_active_display = invalid_date;
}
console.error(`Error: Invalid date string provided: ${last_active}`);
}
} else { // last_active is null or undefined
last_active_display = invalid_date;
}

return {
display_value: last_active_display,
sort_value: last_active_sort_value
};

return { display_value, sort_value };
}


/**
* Generates HTML for the list of domains assigned to a member.
*
* @param {number} num_domains - The number of domains the member is assigned to.
* @param {Array} domain_names - An array of domain names.
* @param {Array} domain_urls - An array of corresponding domain URLs.
* @returns {string} - A string of HTML displaying the domains assigned to the member.
*/
generateDomainsHTML(num_domains, domain_names, domain_urls) {
// Initialize an empty string for the HTML
let domainsHTML = '';

// Only generate HTML if the member has one or more assigned domains
if (num_domains > 0) {
domainsHTML += "<div class='desktop:grid-col-5 margin-bottom-2 desktop:margin-bottom-0'>";
domainsHTML += "<h4 class='margin-y-0 text-primary'>Domains assigned</h4>";
domainsHTML += `<p class='margin-y-0'>This member is assigned to ${num_domains} domains:</p>`;
domainsHTML += "<ul class='usa-list usa-list--unstyled margin-y-0'>";

// Display up to 6 domains with their URLs
for (let i = 0; i < num_domains && i < 6; i++) {
domainsHTML += `<li><a href="${domain_urls[i]}">${domain_names[i]}</a></li>`;
}

domainsHTML += "</ul>";

// If there are more than 6 domains, display a "View assigned domains" link
if (num_domains >= 6) {
domainsHTML += "<p><a href='#'>View assigned domains</a></p>";
}

domainsHTML += "</div>";
}

return domainsHTML;
}

/**
* Generates an HTML string summarizing a user's additional permissions within a portfolio,
* based on the user's permissions and predefined permission choices.
*
* @param {Array} member_permissions - An array of permission strings that the member has.
* @param {Object} UserPortfolioPermissionChoices - An object containing predefined permission choice constants.
* Expected keys include:
* - VIEW_ALL_DOMAINS
* - VIEW_MANAGED_DOMAINS
* - EDIT_REQUESTS
* - VIEW_ALL_REQUESTS
* - EDIT_MEMBERS
* - VIEW_MEMBERS
*
* @returns {string} - A string of HTML representing the user's additional permissions.
* If the user has no specific permissions, it returns a default message
* indicating no additional permissions.
*
* Behavior:
* - The function checks the user's permissions (`member_permissions`) and generates
* corresponding HTML sections based on the permission choices defined in `UserPortfolioPermissionChoices`.
* - Permissions are categorized into domains, requests, and members:
* - Domains: Determines whether the user can view or manage all or assigned domains.
* - Requests: Differentiates between users who can edit requests, view all requests, or have no request privileges.
* - Members: Distinguishes between members who can manage or only view other members.
* - If no relevant permissions are found, the function returns a message stating that the user has no additional permissions.
* - The resulting HTML always includes a header "Additional permissions for this member" and appends the relevant permission descriptions.
*/
generatePermissionsHTML(member_permissions, UserPortfolioPermissionChoices) {
let permissionsHTML = '';

// Check domain-related permissions
if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Domains:</strong> Can view all organization domains. Can manage domains they are assigned to and edit information about the domain (including DNS settings).</p>";
} else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Domains:</strong> Can manage domains they are assigned to and edit information about the domain (including DNS settings).</p>";
}

// Check request-related permissions
if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_REQUESTS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Domain requests:</strong> Can view all organization domain requests. Can create domain requests and modify their own requests.</p>";
} else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Domain requests (view-only):</strong> Can view all organization domain requests. Can't create or modify any domain requests.</p>";
}

// Check member-related permissions
if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_MEMBERS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Members:</strong> Can manage members including inviting new members, removing current members, and assigning domains to members.</p>";
} else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MEMBERS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Members (view-only):</strong> Can view all organizational members. Can't manage any members.</p>";
}

// If no specific permissions are assigned, display a message indicating no additional permissions
if (!permissionsHTML) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><b>No additional permissions:</b> There are no additional permissions for this member.</p>";
}

// Add a permissions header and wrap the entire output in a container
permissionsHTML = "<div class='desktop:grid-col-7'><h4 class='margin-y-0 text-primary'>Additional permissions for this member</h4>" + permissionsHTML + "</div>";

return permissionsHTML;
}

/**
* Loads rows in the members list, as well as updates pagination around the members list
* based on the supplied attributes.
Expand Down Expand Up @@ -2033,47 +2142,11 @@ class MembersTable extends LoadTableBase {
if (member.is_admin)
admin_tagHTML = `<span class="usa-tag margin-left-1 bg-primary">Admin</span>`

// generate html blocks for domains and permissions for the member
let domainsHTML = this.generateDomainsHTML(num_domains, domain_names, domain_urls);
let permissionsHTML = this.generatePermissionsHTML(member_permissions, UserPortfolioPermissionChoices);

// domainsHTML block and permissionsHTML block need to be wrapped with hide/show toggle, Expand

let domainsHTML = '';
if (num_domains > 0) {
domainsHTML += "<div class='desktop:grid-col-5 margin-bottom-2 desktop:margin-bottom-0'>";
domainsHTML += "<h4 class='margin-y-0 text-primary'>Domains assigned</h4>";
domainsHTML += "<p class='margin-y-0'>This member is assigned to " + num_domains + " domains:";
domainsHTML += "<ul class='usa-list usa-list--unstyled margin-y-0'>";
for (let i = 0; i < num_domains && i < 6; i++) {
domainsHTML += `<li><a href="${domain_urls[i]}">${domain_names[i]}</a></li>`;
}
domainsHTML += "</ul>";
if (num_domains >= 6) {
domainsHTML += "<p><a href='#'>View assigned domains</a></p>";
}
domainsHTML += "</div>";
}

let permissionsHTML = '';
if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Domains:</strong> Can view all organization domains. Can manage domains they are assigned to and edit information about the domain (including DNS settings).</p>";
} else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Domains:</strong> Can manage domains they are assigned to and edit information about the domain (including DNS settings).</p>";
}
if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_REQUESTS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Domain requests:</strong> Can view all organization domain requests. Can create domain requests and modify their own requests.</p>";
} else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Domain requests (view-only):</strong> Can view all organization domain requests. Can't create or modify any domain requests.</p>";
}
if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_MEMBERS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Members:</strong> Can manage members including inviting new members, removing current members, and assigning domains to members.";
} else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MEMBERS)) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><strong class='text-base-dark'>Members (view-only):</strong> Can view all organizational members. Can't manage any members.";
}
// if there are no additional permissions, display a no additional permissions message
if (!permissionsHTML) {
permissionsHTML += "<p class='margin-top-1 p--blockquote'><b>No additional permissions:</b> There are no additional permissions for this member.</p>";
}
// add permissions header in all cases
permissionsHTML = "<div class='desktop:grid-col-7'><h4 class='margin-y-0 text-primary'>Additional permissions for this member</h4>" + permissionsHTML + "</div>";

let showMoreButton = '';
const showMoreRow = document.createElement('tr');
if (domainsHTML || permissionsHTML) {
Expand Down
8 changes: 4 additions & 4 deletions src/registrar/tests/test_views_members_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,14 @@ def test_get_portfolio_members_json_authenticated(self):
actual_roles = {role for member in data["members"] for role in member["roles"]}
self.assertEqual(expected_roles, actual_roles)

# Assert that the expected additional permissions are in the actual entire permissions list
expected_additional_permissions = {
UserPortfolioPermissionChoices.VIEW_MEMBERS,
UserPortfolioPermissionChoices.EDIT_MEMBERS,
}
actual_additional_permissions = {
permission for member in data["members"] for permission in member["permissions"]
}
self.assertTrue(expected_additional_permissions.issubset(actual_additional_permissions))
# actual_permissions includes additional permissions as well as permissions from roles
actual_permissions = {permission for member in data["members"] for permission in member["permissions"]}
self.assertTrue(expected_additional_permissions.issubset(actual_permissions))

def test_get_portfolio_invited_json_authenticated(self):
"""Test that portfolio invitees are returned properly for an authenticated user."""
Expand Down

0 comments on commit 07e4960

Please sign in to comment.