diff --git a/CHANGELOG.md b/CHANGELOG.md
index 314e65bd..1537a2b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -100,3 +100,12 @@ For each PR made, an entry should be added to this changelog. It should contain
- Added universal search functionality tests
- Created search pane filter tests
- Added pattern application form tests with validation checks
+
+- affected-urls-page
+ - Description: Added functionality to view affected URLs (both Delta and Curated URLs) for each pattern type (Include, Exclude, Title, Document Type) in a modal view.
+ - Changes:
+ - Created new API endpoints for each pattern type to fetch affected URLs
+ - Added `BaseAffectedURLsViewSet` and pattern-specific views for API endpoints
+ - Implemented modal-based display for affected URLs with dynamic sizing
+ - Enhanced pattern list views with clickable eye icons that shows "Affected URLs" modal
+ - Improved user experience by keeping users on the same page while viewing affected URLs
diff --git a/sde_collections/urls.py b/sde_collections/urls.py
index 9ee77759..dcba1d94 100644
--- a/sde_collections/urls.py
+++ b/sde_collections/urls.py
@@ -16,6 +16,21 @@
router.register(r"document-type-patterns", views.DocumentTypePatternViewSet)
router.register(r"division-patterns", views.DivisionPatternViewSet)
router.register(r"environmental-justice", EnvironmentalJusticeRowViewSet)
+router.register(
+ r"exclude-pattern-affected-urls", views.ExcludePatternAffectedURLsViewSet, basename="exclude-pattern-affected-urls"
+)
+router.register(
+ r"include-pattern-affected-urls", views.IncludePatternAffectedURLsViewSet, basename="include-pattern-affected-urls"
+)
+router.register(
+ r"title-pattern-affected-urls", views.TitlePatternAffectedURLsViewSet, basename="title-pattern-affected-urls"
+)
+router.register(
+ r"documenttype-pattern-affected-urls",
+ views.DocumentTypePatternAffectedURLsViewSet,
+ basename="documenttype-pattern-affected-urls",
+)
+
app_name = "sde_collections"
diff --git a/sde_collections/views.py b/sde_collections/views.py
index fb268170..91b67888 100644
--- a/sde_collections/views.py
+++ b/sde_collections/views.py
@@ -632,3 +632,41 @@ def get(self, request, *args, **kwargs):
"resolved_title_errors": resolved_title_errors,
}
return render(request, "sde_collections/titles_and_errors_list.html", context)
+
+
+class BaseAffectedURLsViewSet(CollectionFilterMixin, viewsets.ModelViewSet):
+
+ pattern_model = None
+ pattern_type = None
+
+ def get_serializer_class(self):
+ url_type = self.request.GET.get("url_type")
+ return DeltaURLSerializer if url_type == "delta" else CuratedURLSerializer
+
+ def get_queryset(self):
+ pattern_id = self.request.GET.get("pattern_id")
+ url_type = self.request.GET.get("url_type")
+ self.pattern = self.pattern_model.objects.get(id=pattern_id)
+ return (
+ self.pattern.get_matching_delta_urls() if url_type == "delta" else self.pattern.get_matching_curated_urls()
+ )
+
+
+class ExcludePatternAffectedURLsViewSet(BaseAffectedURLsViewSet):
+ pattern_model = DeltaExcludePattern
+ pattern_type = "Exclude"
+
+
+class IncludePatternAffectedURLsViewSet(BaseAffectedURLsViewSet):
+ pattern_model = DeltaIncludePattern
+ pattern_type = "Include"
+
+
+class TitlePatternAffectedURLsViewSet(BaseAffectedURLsViewSet):
+ pattern_model = DeltaTitlePattern
+ pattern_type = "Title"
+
+
+class DocumentTypePatternAffectedURLsViewSet(BaseAffectedURLsViewSet):
+ pattern_model = DeltaDocumentTypePattern
+ pattern_type = "Document Type"
diff --git a/sde_indexing_helper/static/css/delta_url_list.css b/sde_indexing_helper/static/css/delta_url_list.css
index 06689207..65865064 100644
--- a/sde_indexing_helper/static/css/delta_url_list.css
+++ b/sde_indexing_helper/static/css/delta_url_list.css
@@ -451,3 +451,50 @@ div.dt-container div.dt-paging ul.pagination {
max-width: 100%;
min-width: 100%;
}
+
+/* Specific styling for affected URLs modal only */
+#affectedURLsModal {
+ padding: 0 !important;
+}
+
+#affectedURLsModal .modal-dialog {
+ max-width: 90%;
+ height: auto;
+ margin: 1.75rem auto;
+}
+
+#affectedURLsModal .modal-content {
+ background-color: #15232E;
+ color: white;
+ width: 100%;
+ height: auto;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+}
+
+#affectedURLsModal .modal-header {
+ border-bottom: 1px solid #3F4A58;
+ margin-bottom: 0px;
+}
+
+#affectedURLsModal .modal-body {
+ flex: 0 1 auto;
+ overflow: auto;
+ padding: 1rem;
+}
+
+#affectedURLsModal .close {
+ color: white;
+}
+
+#affectedURLsModal .dataTables_wrapper {
+ display: flex;
+ flex-direction: column;
+}
+
+#affectedURLsModal .col-md-auto {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
diff --git a/sde_indexing_helper/static/js/delta_url_list.js b/sde_indexing_helper/static/js/delta_url_list.js
index fb214cc1..7464d4a9 100644
--- a/sde_indexing_helper/static/js/delta_url_list.js
+++ b/sde_indexing_helper/static/js/delta_url_list.js
@@ -103,6 +103,23 @@ function modalContents(tableName) {
});
}
+function renderCountWithViewButton(count, patternType, urlType, rowId) {
+ return `
+
+
+ ${count}
+
+
+
+ `;
+}
+
function initializeDataTable() {
var true_icon = 'check';
var false_icon = 'close';
@@ -604,11 +621,17 @@ function initializeDataTable() {
data: "delta_urls_count",
class: "text-center whiteText",
sortable: true,
+ render: function (data, type, row) {
+ return renderCountWithViewButton(data, 'exclude', 'delta', row.id);
+ },
},
{
data: "curated_urls_count",
class: "text-center whiteText",
sortable: true,
+ render: function (data, type, row) {
+ return renderCountWithViewButton(data, 'exclude', 'curated', row.id);
+ },
},
{
data: null,
@@ -690,11 +713,17 @@ function initializeDataTable() {
data: "delta_urls_count",
class: "text-center whiteText",
sortable: true,
+ render: function (data, type, row) {
+ return renderCountWithViewButton(data, 'include', 'delta', row.id);
+ },
},
{
data: "curated_urls_count",
class: "text-center whiteText",
sortable: true,
+ render: function (data, type, row) {
+ return renderCountWithViewButton(data, 'include', 'curated', row.id);
+ },
},
{
data: null,
@@ -773,11 +802,17 @@ function initializeDataTable() {
data: "delta_urls_count",
class: "text-center whiteText",
sortable: true,
+ render: function (data, type, row) {
+ return renderCountWithViewButton(data, 'title', 'delta', row.id);
+ },
},
{
data: "curated_urls_count",
class: "text-center whiteText",
sortable: true,
+ render: function (data, type, row) {
+ return renderCountWithViewButton(data, 'title', 'curated', row.id);
+ },
},
{
data: null,
@@ -856,11 +891,17 @@ function initializeDataTable() {
data: "delta_urls_count",
class: "text-center whiteText",
sortable: true,
+ render: function (data, type, row) {
+ return renderCountWithViewButton(data, 'document-type', 'delta', row.id);
+ },
},
{
data: "curated_urls_count",
class: "text-center whiteText",
sortable: true,
+ render: function (data, type, row) {
+ return renderCountWithViewButton(data, 'document-type', 'curated', row.id);
+ },
},
{
data: null,
@@ -998,6 +1039,7 @@ function setupClickHandlers() {
handleDivisionSelect();
handleExcludeIndividualUrlClick();
handleNewTitleChange();
+ handleShowAffectedURLsListButtonClick();
handleUrlLinkClick();
handleTabsClick();
@@ -2251,3 +2293,98 @@ function handleReindexingStatusSelect() {
});
});
}
+
+function handleShowAffectedURLsListButtonClick() {
+ const PATTERN_ENDPOINTS = {
+ 'exclude': 'exclude-pattern-affected-urls',
+ 'include': 'include-pattern-affected-urls',
+ 'title': 'title-pattern-affected-urls',
+ 'document-type': 'documenttype-pattern-affected-urls'
+ };
+
+ $("body").on("click", ".view-pattern-urls", function() {
+ const matchPatternId = $(this).data("row-id");
+ const patternType = $(this).data("pattern-type");
+ const urlType = $(this).data("url-type");
+ const patternName = $(this).closest('tr').find('td:first').text();
+ const urlCount = $(this).prev('.urlCount').text().trim();
+
+ // Update modal title and pattern info
+ const capitalize = str => str[0].toUpperCase() + str.slice(1);
+ $("#affectedURLsModalTitle").text(`Affected ${capitalize(urlType)} URLs`);
+ $("#patternInfo").text(`
+ ${urlCount} affected URL${urlCount === '1' ? '' : 's'} for ${patternType} pattern:
+ `).append($('').css('color', '#65B1EF').text(patternName));
+
+ if ($.fn.DataTable.isDataTable('#affectedURLsModalTable')) {
+ $('#affectedURLsModalTable').DataTable().destroy();
+ }
+
+ initializeModalDataTable(PATTERN_ENDPOINTS[patternType], matchPatternId, urlType);
+ $("#affectedURLsModal").modal('show');
+
+ });
+}
+
+function initializeModalDataTable(endpoint, patternId, urlType) {
+
+ affected_urls_table = $("#affectedURLsModalTable").DataTable({
+ processing: true,
+ pageLength: 100,
+ colReorder: true,
+ stateSave: true,
+ serverSide: true,
+ orderCellsTop: true,
+ pagingType: "input",
+ paging: true,
+ stateSave: false,
+ rowId: "url",
+ layout: {
+ bottomEnd: "inputPaging",
+ topEnd: null,
+ topStart: {
+ info: true,
+ pageLength: {
+ menu: [
+ [25, 50, 100, 500],
+ ["Show 25", "Show 50", "Show 100", "Show 500"],
+ ],
+ },
+ },
+ },
+ columnDefs: [
+ { orderable: true, targets: "_all" },
+ { orderable: false, targets: "filter-row" },
+ ],
+ orderCellsTop: true,
+ ajax: {
+ url: `/api/${endpoint}/?format=datatables&url_type=${urlType}&pattern_id=${patternId}`,
+ data: function (d) {},
+ complete: function (xhr, status) {},
+ },
+
+ columns: [
+ getURLColumn(),
+ ],
+ });
+
+ $("#affectedURLsFilter").on(
+ "beforeinput",
+ DataTable.util.debounce(function (val) {
+ affected_urls_table.columns(0).search(this.value).draw();
+ }, 200)
+ );
+}
+
+function getURLColumn() {
+ return {
+ data: "url",
+ width: "30%",
+ render: function (data, type, row) {
+ return ``;
+ },
+ };
+}
diff --git a/sde_indexing_helper/templates/sde_collections/delta_urls_list.html b/sde_indexing_helper/templates/sde_collections/delta_urls_list.html
index e17317a3..c286bbcf 100644
--- a/sde_indexing_helper/templates/sde_collections/delta_urls_list.html
+++ b/sde_indexing_helper/templates/sde_collections/delta_urls_list.html
@@ -762,6 +762,32 @@ Are you sure?
+
+
{% endblock content %}
{% block javascripts %}