Skip to content

Commit

Permalink
Merge branch 'main' into ms/3171-error-logs-domainrequest
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt-Spence authored Jan 14, 2025
2 parents 9d33c18 + a0a53ea commit 9640100
Show file tree
Hide file tree
Showing 36 changed files with 1,130 additions and 149 deletions.
28 changes: 28 additions & 0 deletions docs/developer/registry-access.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,31 @@ response = registry._client.transport.receive()
```

This is helpful for debugging situations where epplib is not correctly or fully parsing the XML returned from the registry.

### Adding in a expiring soon domain
The below scenario is if you are NOT in org model mode (`organization_feature` waffle flag is off).

1. Go to the `staging` sandbox and to `/admin`
2. Go to Domains and find a domain that is actually expired by sorting the Expiration Date column
3. Click into the domain to check the expiration date
4. Click into Manage Domain to double check the expiration date as well
5. Now hold onto that domain name, and save it for the command below

6. In a terminal, run these commands:
```
cf ssh getgov-<your-intials>
/tmp/lifecycle/shell
./manage.py shell
from registrar.models import Domain, DomainInvitation
from registrar.models import User
user = User.objects.filter(first_name="<your-first-name>")
domain = Domain.objects.get_or_create(name="<that-domain-here>")
```

7. Go back to `/admin` and create Domain Information for that domain you just added in via the terminal
8. Go to Domain to find it
9. Click Manage Domain
10. Add yourself as domain manager
11. Go to the Registrar page and you should now see the expiring domain

If you want to be in the org model mode, turn the `organization_feature` waffle flag on, and add that domain via Django Admin to a portfolio to be able to view it.
14 changes: 14 additions & 0 deletions src/registrar/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,20 @@ def changelist_view(self, request, extra_context=None):
# Get the filtered values
return super().changelist_view(request, extra_context=extra_context)

def save_model(self, request, obj, form, change):
"""
Override the save_model method.
On creation of a new domain invitation, attempt to retrieve the invitation,
which will be successful if a single User exists for that email; otherwise, will
just continue to create the invitation.
"""
if not change and User.objects.filter(email=obj.email).count() == 1:
# Domain Invitation creation for an existing User
obj.retrieve()
# Call the parent save method to save the object
super().save_model(request, obj, form, change)


class PortfolioInvitationAdmin(ListHeaderAdmin):
"""Custom portfolio invitation admin class."""
Expand Down
2 changes: 1 addition & 1 deletion src/registrar/assets/src/js/getgov/helpers-uswds.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* accessible directly in getgov.min.js
*
*/
export function initializeTooltips() {
export function uswdsInitializeTooltips() {
function checkTooltip() {
// Check that the tooltip library is loaded, and if not, wait and retry
if (window.tooltip && typeof window.tooltip.init === 'function') {
Expand Down
12 changes: 10 additions & 2 deletions src/registrar/assets/src/js/getgov/table-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,14 +375,21 @@ export class BaseTable {
*/
loadModals(page, total, unfiltered_total) {}

/**
* Loads tooltips + sets up event listeners
* "Activates" the tooltips after the DOM updates
* Utilizes "uswdsInitializeTooltips"
*/
initializeTooltips() {}

/**
* Allows us to customize the table display based on specific conditions and a user's permissions
* Dynamically manages the visibility set up of columns, adding/removing headers
* (ie if a domain request is deleteable, we include the kebab column or if a user has edit permissions
* for a member, they will also see the kebab column)
* @param {Object} dataObjects - Data which contains info on domain requests or a user's permission
* Currently returns a dictionary of either:
* - "needsAdditionalColumn": If a new column should be displayed
* - "hasAdditionalActions": If additional elements need to be added to the Action column
* - "UserPortfolioPermissionChoices": A user's portfolio permission choices
*/
customizeTable(dataObjects){ return {}; }
Expand All @@ -406,7 +413,7 @@ export class BaseTable {
* Returns either: data.members, data.domains or data.domain_requests
* @param {Object} dataObject - The data used to populate the row content
* @param {HTMLElement} tbody - The table body to which the new row is appended to
* @param {Object} customTableOptions - Additional options for customizing row appearance (ie needsAdditionalColumn)
* @param {Object} customTableOptions - Additional options for customizing row appearance (ie hasAdditionalActions)
*/
addRow(dataObject, tbody, customTableOptions) {
throw new Error('addRow must be defined');
Expand Down Expand Up @@ -471,6 +478,7 @@ export class BaseTable {
this.initCheckboxListeners();

this.loadModals(data.page, data.total, data.unfiltered_total);
this.initializeTooltips();

// Do not scroll on first page load
if (scroll)
Expand Down
45 changes: 15 additions & 30 deletions src/registrar/assets/src/js/getgov/table-domain-requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,8 @@ export class DomainRequestsTable extends BaseTable {
// Manage "export as CSV" visibility for domain requests
this.toggleExportButton(data.domain_requests);

let needsDeleteColumn = data.domain_requests.some(request => request.is_deletable);

// Remove existing delete th and td if they exist
let existingDeleteTh = document.querySelector('.delete-header');
if (!needsDeleteColumn) {
if (existingDeleteTh)
existingDeleteTh.remove();
} else {
if (!existingDeleteTh) {
const delheader = document.createElement('th');
delheader.setAttribute('scope', 'col');
delheader.setAttribute('role', 'columnheader');
delheader.setAttribute('class', 'delete-header width-5');
delheader.innerHTML = `
<span class="usa-sr-only">Delete Action</span>`;
let tableHeaderRow = this.tableWrapper.querySelector('thead tr');
tableHeaderRow.appendChild(delheader);
}
}
return { 'needsAdditionalColumn': needsDeleteColumn };
let isDeletable = data.domain_requests.some(request => request.is_deletable);
return { 'hasAdditionalActions': isDeletable };
}

addRow(dataObject, tbody, customTableOptions) {
Expand All @@ -88,6 +70,7 @@ export class DomainRequestsTable extends BaseTable {
<span class="usa-sr-only">Domain request cannot be deleted now. Edit the request for more information.</span>`;

let markupCreatorRow = '';


if (this.portfolioValue) {
markupCreatorRow = `
Expand All @@ -98,7 +81,7 @@ export class DomainRequestsTable extends BaseTable {
}

if (request.is_deletable) {
// 1st path: Just a modal trigger in any screen size for non-org users
// 1st path (non-org): Just a modal trigger in any screen size for non-org users
modalTrigger = `
<a
role="button"
Expand All @@ -116,7 +99,7 @@ export class DomainRequestsTable extends BaseTable {
// Request is deletable, modal and modalTrigger are built. Now check if we are on the portfolio requests page (by seeing if there is a portfolio value) and enhance the modalTrigger accordingly
if (this.portfolioValue) {

// 2nd path: Just a modal trigger on mobile for org users or kebab + accordion with nested modal trigger on desktop for org users
// 2nd path (org model): Just a modal trigger on mobile for org users or kebab + accordion with nested modal trigger on desktop for org users
modalTrigger = generateKebabHTML('delete-domain', request.id, 'Delete', domainName);
}
}
Expand All @@ -133,15 +116,17 @@ export class DomainRequestsTable extends BaseTable {
<td data-label="Status">
${request.status}
</td>
<td>
<a href="${actionUrl}">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#${request.svg_icon}"></use>
</svg>
${actionLabel} <span class="usa-sr-only">${request.requested_domain ? request.requested_domain : 'New domain request'}</span>
</a>
<td class="${ this.portfolioValue ? '' : "width-quarter"}">
<div class="tablet:display-flex tablet:flex-row">
<a href="${actionUrl}" ${customTableOptions.hasAdditionalActions ? "class='margin-right-2'" : ''}>
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#${request.svg_icon}"></use>
</svg>
${actionLabel} <span class="usa-sr-only">${request.requested_domain ? request.requested_domain : 'New domain request'}</span>
</a>
${customTableOptions.hasAdditionalActions ? modalTrigger : ''}
</div>
</td>
${customTableOptions.needsAdditionalColumn ? '<td>'+modalTrigger+'</td>' : ''}
`;
tbody.appendChild(row);
if (request.is_deletable) DomainRequestsTable.addDomainRequestsModal(request.requested_domain, request.id, request.created_at, tbody);
Expand Down
6 changes: 5 additions & 1 deletion src/registrar/assets/src/js/getgov/table-domains.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BaseTable } from './table-base.js';
import { uswdsInitializeTooltips } from './helpers-uswds.js';

export class DomainsTable extends BaseTable {

Expand Down Expand Up @@ -55,7 +56,7 @@ export class DomainsTable extends BaseTable {
</svg>
</td>
${markupForSuborganizationRow}
<td>
<td class="${ this.portfolioValue ? '' : "width-quarter"}">
<a href="${actionUrl}">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#${domain.svg_icon}"></use>
Expand All @@ -66,6 +67,9 @@ export class DomainsTable extends BaseTable {
`;
tbody.appendChild(row);
}
initializeTooltips() {
uswdsInitializeTooltips();
}
}

export function initDomainsTable() {
Expand Down
8 changes: 4 additions & 4 deletions src/registrar/assets/src/js/getgov/table-members.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class MembersTable extends BaseTable {
tableHeaderRow.appendChild(extraActionsHeader);
}
return {
'needsAdditionalColumn': hasEditPermission,
'hasAdditionalActions': hasEditPermission,
'UserPortfolioPermissionChoices' : data.UserPortfolioPermissionChoices
};
}
Expand All @@ -78,7 +78,7 @@ export class MembersTable extends BaseTable {
const num_domains = member.domain_urls.length;
const last_active = this.handleLastActive(member.last_active);
let cancelInvitationButton = member.type === "invitedmember" ? "Cancel invitation" : "Remove member";
const kebabHTML = customTableOptions.needsAdditionalColumn ? generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `for ${member.name}`): '';
const kebabHTML = customTableOptions.hasAdditionalActions ? generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `for ${member.name}`): '';

const row = document.createElement('tr');

Expand Down Expand Up @@ -129,15 +129,15 @@ export class MembersTable extends BaseTable {
${member.action_label} <span class="usa-sr-only">${member.name}</span>
</a>
</td>
${customTableOptions.needsAdditionalColumn ? '<td>'+kebabHTML+'</td>' : ''}
${customTableOptions.hasAdditionalActions ? '<td>'+kebabHTML+'</td>' : ''}
`;
tbody.appendChild(row);
if (domainsHTML || permissionsHTML) {
tbody.appendChild(showMoreRow);
}
// 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
if (customTableOptions.needsAdditionalColumn) MembersTable.addMemberDeleteModal(num_domains, member.email || "Samwise Gamgee", member_delete_url, unique_id, row);
if (customTableOptions.hasAdditionalActions) MembersTable.addMemberDeleteModal(num_domains, member.email || "Samwise Gamgee", member_delete_url, unique_id, row);
}

/**
Expand Down
34 changes: 34 additions & 0 deletions src/registrar/assets/src/sass/_theme/_admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,40 @@ div#content > h2 {
}
}

.module {
.margin-left-0 {
margin-left: 0;
}
.margin-top-0 {
margin-top: 0;
}
.padding-left-0 {
padding-left: 0;
}
}

.admin-list-inline {
li {
float: left;
padding-top: 0;
margin-right: 4px;
}
li:not(:last-child)::after {
content: ",";
}
}

.form-row {
.margin-y-0 {
margin-top: 0;
margin-bottom: 0;
}
.padding-y-0 {
padding-top: 0;
padding-bottom: 0;
}
}

// Fixes a display issue where the list was entirely white, or had too much whitespace
.select2-dropdown {
display: inline-grid !important;
Expand Down
4 changes: 4 additions & 0 deletions src/registrar/assets/src/sass/_theme/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -281,4 +281,8 @@ abbr[title] {

.maxw-fit-content {
max-width: fit-content;
}

.width-quarter {
width: 25%;
}
5 changes: 5 additions & 0 deletions src/registrar/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,11 @@
views.DomainSecurityEmailView.as_view(),
name="domain-security-email",
),
path(
"domain/<int:pk>/renewal",
views.DomainRenewalView.as_view(),
name="domain-renewal",
),
path(
"domain/<int:pk>/users/add",
views.DomainAddUserView.as_view(),
Expand Down
19 changes: 17 additions & 2 deletions src/registrar/fixtures/fixtures_requests.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import timedelta
from datetime import datetime, timedelta
from django.utils import timezone
import logging
import random
Expand Down Expand Up @@ -126,7 +126,22 @@ def _set_non_foreign_key_fields(cls, request: DomainRequest, request_dict: dict)
# 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"
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_dict["federal_type"]
if "federal_type" in request_dict
Expand Down
1 change: 1 addition & 0 deletions src/registrar/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
DomainDsdataFormset,
DomainDsdataForm,
DomainSuborganizationForm,
DomainRenewalForm,
)
from .portfolio import (
PortfolioOrgAddressForm,
Expand Down
12 changes: 12 additions & 0 deletions src/registrar/forms/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,3 +661,15 @@ def clean(self):
extra=0,
can_delete=True,
)


class DomainRenewalForm(forms.Form):
"""Form making sure domain renewal ack is checked"""

is_policy_acknowledged = forms.BooleanField(
required=True,
label="I have read and agree to the requirements for operating a .gov domain.",
error_messages={
"required": "Check the box if you read and agree to the requirements for operating a .gov domain."
},
)
Loading

0 comments on commit 9640100

Please sign in to comment.