From 5e6f405b95f38d4be26011c41d7ba8a88f481397 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Wed, 5 Jun 2024 01:19:47 -0400
Subject: [PATCH 01/22] implement seach on domains and requests
---
src/registrar/assets/js/get-gov.js | 134 +++++++++++++-----
src/registrar/assets/js/uswds-edited.js | 8 +-
src/registrar/assets/sass/_theme/_tables.scss | 4 +-
src/registrar/templates/home.html | 80 +++++++++--
src/registrar/views/domain_requests_json.py | 11 ++
src/registrar/views/domains_json.py | 9 ++
6 files changed, 197 insertions(+), 49 deletions(-)
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index 0d594b315..cc24f7df7 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -918,8 +918,9 @@ function ScrollToElement(attributeName, attributeValue) {
* @param {boolean} hasPrevious - Whether there is a page before the current page.
* @param {boolean} hasNext - Whether there is a page after the current page.
* @param {number} totalItems - The total number of items.
+ * @param {string} searchTerm - The search term
*/
-function updatePagination(itemName, paginationSelector, counterSelector, headerAnchor, loadPageFunction, currentPage, numPages, hasPrevious, hasNext, totalItems) {
+function updatePagination(itemName, paginationSelector, counterSelector, headerAnchor, loadPageFunction, currentPage, numPages, hasPrevious, hasNext, totalItems, searchTerm) {
const paginationContainer = document.querySelector(paginationSelector);
const paginationCounter = document.querySelector(counterSelector);
const paginationButtons = document.querySelector(`${paginationSelector} .usa-pagination__list`);
@@ -932,7 +933,7 @@ function updatePagination(itemName, paginationSelector, counterSelector, headerA
// Counter should only be displayed if there is more than 1 item
paginationContainer.classList.toggle('display-none', totalItems < 1);
- paginationCounter.innerHTML = `${totalItems} ${itemName}${totalItems > 1 ? 's' : ''}`;
+ paginationCounter.innerHTML = `${totalItems} ${itemName}${totalItems > 1 ? 's' : ''}${searchTerm ? ' for ' + searchTerm : ''}`;
if (hasPrevious) {
const prevPageItem = document.createElement('li');
@@ -1018,6 +1019,28 @@ function updatePagination(itemName, paginationSelector, counterSelector, headerA
}
}
+const updateDisplay = (data, dataWrapper, noDataWrapper, noSearchResultsWrapper) => {
+ const { unfiltered_total, total } = data;
+
+ const showElement = (element) => element.classList.remove('display-none');
+ const hideElement = (element) => element.classList.add('display-none');
+
+ if (unfiltered_total) {
+ if (total) {
+ showElement(dataWrapper);
+ hideElement(noSearchResultsWrapper);
+ hideElement(noDataWrapper);
+ } else {
+ hideElement(dataWrapper);
+ showElement(noSearchResultsWrapper);
+ hideElement(noDataWrapper);
+ }
+ } else {
+ hideElement(dataWrapper);
+ hideElement(noSearchResultsWrapper);
+ showElement(noDataWrapper);
+ }
+};
/**
* An IIFE that listens for DOM Content to be loaded, then executes. This function
@@ -1025,13 +1048,19 @@ function updatePagination(itemName, paginationSelector, counterSelector, headerA
*
*/
document.addEventListener('DOMContentLoaded', function() {
- let domainsWrapper = document.querySelector('.domains-wrapper');
+ let domainsWrapper = document.querySelector('.domains__table-wrapper');
if (domainsWrapper) {
let currentSortBy = 'id';
let currentOrder = 'asc';
- let noDomainsWrapper = document.querySelector('.no-domains-wrapper');
+ let noDomainsWrapper = document.querySelector('.domains__no-data');
+ let noSearchResultsWrapper = document.querySelector('.domains__no-search-results');
let hasLoaded = false;
+ let currentSearchTerm = ''
+ let domainsSearchInput = document.getElementById('domains__search-field');
+ let domainsSearchSubmit = document.getElementById('domains__search-field-submit');
+ let tableHeaders = document.querySelectorAll('.domains__table th[data-sortable]');
+ let tableAnnouncementRegion = document.querySelector('.domains__table-wrapper .usa-table__announcement-region')
/**
* Loads rows in the domains list, as well as updates pagination around the domains list
@@ -1040,10 +1069,11 @@ document.addEventListener('DOMContentLoaded', function() {
* @param {*} sortBy - the sort column option
* @param {*} order - the sort order {asc, desc}
* @param {*} loaded - control for the scrollToElement functionality
+ * @param {*} searchTerm - the search term
*/
- function loadDomains(page, sortBy = currentSortBy, order = currentOrder, loaded = hasLoaded) {
+ function loadDomains(page, sortBy = currentSortBy, order = currentOrder, loaded = hasLoaded, searchTerm = currentSearchTerm) {
//fetch json of page of domains, given page # and sort
- fetch(`/get-domains-json/?page=${page}&sort_by=${sortBy}&order=${order}`)
+ fetch(`/get-domains-json/?page=${page}&sort_by=${sortBy}&order=${order}&search_term=${searchTerm}`)
.then(response => response.json())
.then(data => {
if (data.error) {
@@ -1051,17 +1081,11 @@ document.addEventListener('DOMContentLoaded', function() {
return;
}
- // handle the display of proper messaging in the event that no domains exist in the list
- if (data.domains.length) {
- domainsWrapper.classList.remove('display-none');
- noDomainsWrapper.classList.add('display-none');
- } else {
- domainsWrapper.classList.add('display-none');
- noDomainsWrapper.classList.remove('display-none');
- }
+ // handle the display of proper messaging in the event that no domains exist in the list or search returns no results
+ updateDisplay(data, domainsWrapper, noDomainsWrapper, noSearchResultsWrapper);
// identify the DOM element where the domain list will be inserted into the DOM
- const domainList = document.querySelector('.dotgov-table__registered-domains tbody');
+ const domainList = document.querySelector('.domains__table tbody');
domainList.innerHTML = '';
data.domains.forEach(domain => {
@@ -1122,7 +1146,8 @@ document.addEventListener('DOMContentLoaded', function() {
data.num_pages,
data.has_previous,
data.has_next,
- data.total
+ data.total,
+ currentSearchTerm
);
currentSortBy = sortBy;
currentOrder = order;
@@ -1133,7 +1158,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Add event listeners to table headers for sorting
- document.querySelectorAll('.dotgov-table__registered-domains th[data-sortable]').forEach(header => {
+ tableHeaders.forEach(header => {
header.addEventListener('click', function() {
const sortBy = this.getAttribute('data-sortable');
let order = 'asc';
@@ -1147,6 +1172,23 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
+ domainsSearchSubmit.addEventListener('click', function(e) {
+ e.preventDefault();
+ currentSearchTerm = domainsSearchInput.value;
+ loadDomains(1, 'id', 'asc');
+ resetheaders();
+ })
+
+ // Reset UI and accessibility
+ function resetheaders() {
+ tableHeaders.forEach(header => {
+ // unset sort UI in headers
+ window.table.unsetHeader(header);
+ });
+ // Reset the announcement region
+ tableAnnouncementRegion.innerHTML = '';
+ }
+
// Load the first page initially
loadDomains(1);
}
@@ -1157,10 +1199,13 @@ const utcDateString = (dateString) => {
const utcYear = date.getUTCFullYear();
const utcMonth = date.toLocaleString('en-US', { month: 'short', timeZone: 'UTC' });
const utcDay = date.getUTCDate().toString().padStart(2, '0');
- const utcHours = date.getUTCHours().toString().padStart(2, '0');
+ let utcHours = date.getUTCHours();
const utcMinutes = date.getUTCMinutes().toString().padStart(2, '0');
-
- return `${utcMonth} ${utcDay}, ${utcYear}, ${utcHours}:${utcMinutes} UTC`;
+
+ const ampm = utcHours >= 12 ? 'PM' : 'AM';
+ utcHours = utcHours % 12 || 12; // Convert to 12-hour format, '0' hours should be '12'
+
+ return `${utcMonth} ${utcDay}, ${utcYear}, ${utcHours}:${utcMinutes} ${ampm} UTC`;
};
/**
@@ -1169,13 +1214,19 @@ const utcDateString = (dateString) => {
*
*/
document.addEventListener('DOMContentLoaded', function() {
- let domainRequestsWrapper = document.querySelector('.domain-requests-wrapper');
+ let domainRequestsWrapper = document.querySelector('.domain-requests__table-wrapper');
if (domainRequestsWrapper) {
let currentSortBy = 'id';
let currentOrder = 'asc';
- let noDomainRequestsWrapper = document.querySelector('.no-domain-requests-wrapper');
+ let noDomainRequestsWrapper = document.querySelector('.domain-requests__no-data');
+ let noSearchResultsWrapper = document.querySelector('.domain-requests__no-search-results');
let hasLoaded = false;
+ let currentSearchTerm = ''
+ let domainRequestsSearchInput = document.getElementById('domain-requests__search-field');
+ let domainRequestsSearchSubmit = document.getElementById('domain-requests__search-field-submit');
+ let tableHeaders = document.querySelectorAll('.domain-requests__table th[data-sortable]');
+ let tableAnnouncementRegion = document.querySelector('.domain-requests__table-wrapper .usa-table__announcement-region')
/**
* Loads rows in the domain requests list, as well as updates pagination around the domain requests list
@@ -1184,10 +1235,11 @@ document.addEventListener('DOMContentLoaded', function() {
* @param {*} sortBy - the sort column option
* @param {*} order - the sort order {asc, desc}
* @param {*} loaded - control for the scrollToElement functionality
+ * @param {*} searchTerm - the search term
*/
- function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, loaded = hasLoaded) {
+ function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder, loaded = hasLoaded, searchTerm = currentSearchTerm) {
//fetch json of page of domain requests, given page # and sort
- fetch(`/get-domain-requests-json/?page=${page}&sort_by=${sortBy}&order=${order}`)
+ fetch(`/get-domain-requests-json/?page=${page}&sort_by=${sortBy}&order=${order}&search_term=${searchTerm}`)
.then(response => response.json())
.then(data => {
if (data.error) {
@@ -1195,17 +1247,11 @@ document.addEventListener('DOMContentLoaded', function() {
return;
}
- // handle the display of proper messaging in the event that no domain requests exist in the list
- if (data.domain_requests.length) {
- domainRequestsWrapper.classList.remove('display-none');
- noDomainRequestsWrapper.classList.add('display-none');
- } else {
- domainRequestsWrapper.classList.add('display-none');
- noDomainRequestsWrapper.classList.remove('display-none');
- }
+ // handle the display of proper messaging in the event that no requests exist in the list or search returns no results
+ updateDisplay(data, domainRequestsWrapper, noDomainRequestsWrapper, noSearchResultsWrapper);
// identify the DOM element where the domain request list will be inserted into the DOM
- const tbody = document.querySelector('.dotgov-table__domain-requests tbody');
+ const tbody = document.querySelector('.domain-requests__table tbody');
tbody.innerHTML = '';
// remove any existing modal elements from the DOM so they can be properly re-initialized
@@ -1272,7 +1318,8 @@ document.addEventListener('DOMContentLoaded', function() {
data.num_pages,
data.has_previous,
data.has_next,
- data.total
+ data.total,
+ currentSearchTerm
);
currentSortBy = sortBy;
currentOrder = order;
@@ -1281,7 +1328,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
// Add event listeners to table headers for sorting
- document.querySelectorAll('.dotgov-table__domain-requests th[data-sortable]').forEach(header => {
+ tableHeaders.forEach(header => {
header.addEventListener('click', function() {
const sortBy = this.getAttribute('data-sortable');
let order = 'asc';
@@ -1294,6 +1341,23 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
+ domainRequestsSearchSubmit.addEventListener('click', function(e) {
+ e.preventDefault();
+ currentSearchTerm = domainRequestsSearchInput.value;
+ loadDomainRequests(1, 'id', 'asc');
+ resetheaders();
+ })
+
+ // Reset UI and accessibility
+ function resetheaders() {
+ tableHeaders.forEach(header => {
+ // unset sort UI in headers
+ window.table.unsetHeader(header);
+ });
+ // Reset the announcement region
+ tableAnnouncementRegion.innerHTML = '';
+ }
+
// Load the first page initially
loadDomainRequests(1);
}
diff --git a/src/registrar/assets/js/uswds-edited.js b/src/registrar/assets/js/uswds-edited.js
index e73f3b6c0..556488554 100644
--- a/src/registrar/assets/js/uswds-edited.js
+++ b/src/registrar/assets/js/uswds-edited.js
@@ -5709,9 +5709,15 @@ const table = behavior({
},
TABLE,
SORTABLE_HEADER,
- SORT_BUTTON
+ SORT_BUTTON,
+ // DOTGOV: Export unsetSort
+ unsetHeader(header) {
+ unsetSort(header);
+ }
});
module.exports = table;
+// DOTGOV: modified uswds.js to add table module to window so that it is accessible to other js
+window.table = table;
},{"../../uswds-core/src/js/config":35,"../../uswds-core/src/js/events":36,"../../uswds-core/src/js/utils/behavior":45,"../../uswds-core/src/js/utils/sanitizer":50,"../../uswds-core/src/js/utils/select":53}],32:[function(require,module,exports){
"use strict";
diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss
index 26d90d291..a5eb5a4e0 100644
--- a/src/registrar/assets/sass/_theme/_tables.scss
+++ b/src/registrar/assets/sass/_theme/_tables.scss
@@ -98,7 +98,7 @@
}
}
@media (min-width: 1040px){
- .dotgov-table__domain-requests {
+ .domain-requests__table {
th:nth-of-type(1) {
width: 200px;
}
@@ -122,7 +122,7 @@
}
@media (min-width: 1040px){
- .dotgov-table__registered-domains {
+ .domains__table {
th:nth-of-type(1) {
width: 200px;
}
diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html
index fd54769a8..c3877ca3f 100644
--- a/src/registrar/templates/home.html
+++ b/src/registrar/templates/home.html
@@ -23,10 +23,35 @@
-
- Domains
-
-
+
+
+
+
Your registered domains
@@ -50,7 +75,7 @@
aria-live="polite"
>
-
+
You don't have any registered domains.
@@ -61,6 +86,10 @@
+
+
Nothing, nada, zilch.
+
Reset your search or try a different search term.
+
-
- Domain requests
-
-
+
+
+
+
Domain requests
+
+
+
+
+
Your domain requests
@@ -129,9 +183,13 @@
{% endfor %}
-
+
You haven't requested any domains.
-
+
+
+
Nothing, nada, zilch.
+
Reset your search or try a different search term.
+
@@ -147,41 +150,6 @@
class="usa-sr-only usa-table__announcement-region"
aria-live="polite"
>
-
- {% for domain_request in domain_requests %}
- {% if has_deletable_domain_requests %}
- {% if domain_request.status == domain_request.DomainRequestStatus.STARTED or domain_request.status == domain_request.DomainRequestStatus.WITHDRAWN %}
-
-
-
- {% endif %}
- {% endif %}
- {% endfor %}
-
You haven't requested any domains.
diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py
index d2e554255..8b8c71dcc 100644
--- a/src/registrar/views/index.py
+++ b/src/registrar/views/index.py
@@ -8,46 +8,7 @@ def index(request):
context = {}
if request.user.is_authenticated:
- # Get all domain requests the user has access to
- domain_requests, deletable_domain_requests = _get_domain_requests(request)
-
- context["domain_requests"] = domain_requests
-
- # Determine if the user will see domain requests that they can delete
- has_deletable_domain_requests = deletable_domain_requests.exists()
- context["has_deletable_domain_requests"] = has_deletable_domain_requests
-
# This is a django waffle flag which toggles features based off of the "flag" table
context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature")
- # If they can delete domain requests, add the delete button to the context
- if has_deletable_domain_requests:
- # Add the delete modal button to the context
- modal_button = (
- '
'
- )
- context["modal_button"] = modal_button
-
return render(request, "home.html", context)
-
-
-def _get_domain_requests(request):
- """Given the current request,
- get all DomainRequests that are associated with the UserDomainRole object.
-
- Returns a tuple of all domain requests, and those that are deletable by the user.
- """
- # Let's exclude the approved domain requests since our
- # domain_requests context will be used to populate
- # the active domain requests table
- domain_requests = DomainRequest.objects.filter(creator=request.user).exclude(
- status=DomainRequest.DomainRequestStatus.APPROVED
- )
-
- # Create a placeholder DraftDomain for each incomplete draft
- valid_statuses = [DomainRequest.DomainRequestStatus.STARTED, DomainRequest.DomainRequestStatus.WITHDRAWN]
- deletable_domain_requests = domain_requests.filter(status__in=valid_statuses)
-
- return (domain_requests, deletable_domain_requests)
From 8d8c31f37a1af0f740b60aa8bad2dca1b275d8db Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Wed, 5 Jun 2024 15:43:23 -0400
Subject: [PATCH 03/22] additional work on delete
---
src/registrar/assets/js/get-gov.js | 36 ++++++++++++++++-----------
src/registrar/views/domain_request.py | 8 ++++--
2 files changed, 27 insertions(+), 17 deletions(-)
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index 4100477b8..1371af45b 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -1229,7 +1229,7 @@ document.addEventListener('DOMContentLoaded', function() {
let tableHeaders = document.querySelectorAll('.domain-requests__table th[data-sortable]');
let tableAnnouncementRegion = document.querySelector('.domain-requests__table-wrapper .usa-table__announcement-region')
- function deleteDomainRequest(domainRequestPk) {
+ function deleteDomainRequest(domainRequestPk,pageToDisplay) {
const csrfToken = getCsrfToken();
// Create FormData object and append the CSRF token
@@ -1247,12 +1247,13 @@ document.addEventListener('DOMContentLoaded', function() {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
- return response.json();
- })
- .then(data => {
- console.log('response', data);
- // Perform any additional actions, e.g., updating the UI
+ loadDomainRequests(pageToDisplay, currentSortBy, currentOrder, hasLoaded, currentSearchTerm);
+ //return response.json();
})
+ // .then(data => {
+ // console.log('response', data);
+ // // Perform any additional actions, e.g., updating the UI
+ // })
.catch(error => console.error('Error fetching domain requests:', error));
}
@@ -1361,7 +1362,7 @@ document.addEventListener('DOMContentLoaded', function() {
const modalSubmit = `
`
@@ -1443,17 +1444,22 @@ document.addEventListener('DOMContentLoaded', function() {
// initialize modals immediately after the DOM content is updated
initializeModals();
- let subbmitButtons = document.querySelectorAll('.usa-moda__submit');
- subbmitButtons.forEach(button => {
- button.addEventListener('click', function() {
- pk = button.getAttribute('data-pk');
- console.log('pk ' + pk);
- deleteDomainRequest(pk);
- loadDomainRequests(1, 'id', 'asc');
+ const modals = document.querySelectorAll('.usa-modal__content');
+
+ modals.forEach(modal => {
+ const submitButton = modal.querySelector('.usa-modal__submit');
+ const closeButton = modal.querySelector('.usa-modal__close');
+ submitButton.addEventListener('click', function() {
+ pk = submitButton.getAttribute('data-pk');
+ closeButton.click();
+ let pageToDisplay = data.page;
+ if (data.total == 1 && data.unfiltered_total > 1) {
+ pageToDisplay--;
+ }
+ deleteDomainRequest(pk, pageToDisplay);
});
});
-
if (hasLoaded)
ScrollToElement('id', 'domain-requests-header');
diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py
index 95a139211..1cd23d803 100644
--- a/src/registrar/views/domain_request.py
+++ b/src/registrar/views/domain_request.py
@@ -798,8 +798,11 @@ class DomainRequestDeleteView(DomainRequestPermissionDeleteView):
domain_request: DomainRequest = self.get_object()
contacts_to_delete, duplicates = self._get_orphaned_contacts(domain_request)
+ self.object = self.get_object()
+ self.object.delete()
+
# Delete the DomainRequest
- response = super().post(request, *args, **kwargs)
+ # response = super().post(request, *args, **kwargs)
# Delete orphaned contacts - but only for if they are not associated with a user
Contact.objects.filter(id__in=contacts_to_delete, user=None).delete()
@@ -811,7 +814,8 @@ class DomainRequestDeleteView(DomainRequestPermissionDeleteView):
duplicates_to_delete, _ = self._get_orphaned_contacts(domain_request, check_db=True)
Contact.objects.filter(id__in=duplicates_to_delete, user=None).delete()
- return response
+ # Return a 200 response with an empty body
+ return HttpResponse(status=200)
def _get_orphaned_contacts(self, domain_request: DomainRequest, check_db=False):
"""
From 5c3459b7c7e728bc77ff6715e7ab0d11185ca8f6 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Wed, 5 Jun 2024 16:08:45 -0400
Subject: [PATCH 04/22] wip
---
src/registrar/assets/js/get-gov.js | 21 +++++++++++++++------
src/registrar/templates/home.html | 2 +-
2 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index 1371af45b..b5d006f03 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -1189,6 +1189,20 @@ document.addEventListener('DOMContentLoaded', function() {
tableAnnouncementRegion.innerHTML = '';
}
+ function resetSearch() {
+ domainsSearchInput.value = '';
+ loadDomains(1, 'id', 'asc', hasLoaded, '');
+
+ resetheaders();
+ }
+
+ resetButton = document.querySelector('.domains__reset-button');
+ if (resetButton) {
+ resetButton.addEventListener('click', function() {
+ resetSearch();
+ });
+ }
+
// Load the first page initially
loadDomains(1);
}
@@ -1248,19 +1262,14 @@ document.addEventListener('DOMContentLoaded', function() {
throw new Error(`HTTP error! status: ${response.status}`);
}
loadDomainRequests(pageToDisplay, currentSortBy, currentOrder, hasLoaded, currentSearchTerm);
- //return response.json();
})
- // .then(data => {
- // console.log('response', data);
- // // Perform any additional actions, e.g., updating the UI
- // })
.catch(error => console.error('Error fetching domain requests:', error));
}
// Helper function to get the CSRF token from the cookie
function getCsrfToken() {
return document.querySelector('input[name="csrfmiddlewaretoken"]').value;
-}
+ }
/**
diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html
index f0bed41c4..94b95e7fc 100644
--- a/src/registrar/templates/home.html
+++ b/src/registrar/templates/home.html
@@ -91,7 +91,7 @@
Nothing, nada, zilch.
-
Reset your search or try a different search term.
+
or try a different search term.