Skip to content

Commit ec73056

Browse files
committed
feat(ui): implement toast history management and error detail modal
- Added functionality to manage toast notifications history, allowing users to view past notifications. - Introduced a modal for displaying detailed error messages, enhancing user experience during error handling. - Updated UI components to include a button for accessing toast history and integrated event listeners for modal interactions. - Refactored existing toast creation logic to support history tracking and badge updates. - Added unit tests for toast history functions to ensure reliability and correctness.
1 parent 41fda08 commit ec73056

23 files changed

Lines changed: 9717 additions & 104 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ api_validation_report.txt
4444
# UI
4545
ui/node_modules
4646
ui/coverage
47+
.playwright*
48+
node_modules/
4749

4850
# Docs build caches
4951
.docs

app/src/main/resources/ui/connection/connection.html

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,71 @@ <h2>Create new connections</h2>
4343
<h3>Existing connections</h3>
4444
<div class="accordion" id="connections-list"></div>
4545
</div>
46-
<div id="toast-container" class="toast-container position-fixed bottom-0 end-0 p-3" style="z-index: 11"></div>
46+
<!-- Toast History Button -->
47+
<button id="toast-history-btn" class="btn btn-outline-secondary position-fixed" style="bottom: 20px; right: 80px; z-index: 1050; border-radius: 50%; width: 45px; height: 45px; padding: 0;" title="View notification history" data-bs-toggle="modal" data-bs-target="#toast-history-modal">
48+
<i class="bi bi-bell"></i>
49+
<span id="toast-history-badge" class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="display: none; font-size: 0.6rem;"></span>
50+
</button>
51+
52+
<!-- Toast History Modal -->
53+
<div class="modal fade" id="toast-history-modal" tabindex="-1" aria-labelledby="toast-history-modal-label" aria-hidden="true">
54+
<div class="modal-dialog modal-lg modal-dialog-scrollable">
55+
<div class="modal-content">
56+
<div class="modal-header">
57+
<h5 class="modal-title" id="toast-history-modal-label">Notification History</h5>
58+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
59+
</div>
60+
<div class="modal-body" id="toast-history-body">
61+
<p class="text-muted text-center">No notifications yet</p>
62+
</div>
63+
<div class="modal-footer">
64+
<button type="button" class="btn btn-outline-danger" id="clear-toast-history">Clear History</button>
65+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
66+
</div>
67+
</div>
68+
</div>
69+
</div>
70+
71+
<!-- Error Detail Modal -->
72+
<div class="modal fade" id="error-detail-modal" tabindex="-1" aria-labelledby="error-detail-modal-label" aria-hidden="true">
73+
<div class="modal-dialog modal-lg modal-dialog-scrollable">
74+
<div class="modal-content">
75+
<div class="modal-header bg-danger text-white">
76+
<h5 class="modal-title" id="error-detail-modal-label"><i class="bi bi-exclamation-triangle me-2"></i><span id="error-detail-title">Error Details</span></h5>
77+
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
78+
</div>
79+
<div class="modal-body">
80+
<pre id="error-detail-body" class="bg-light p-3 rounded" style="max-height: 400px; overflow-y: auto;"></pre>
81+
</div>
82+
<div class="modal-footer">
83+
<button type="button" class="btn btn-outline-secondary" id="copy-error-btn"><i class="bi bi-clipboard me-1"></i>Copy to Clipboard</button>
84+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
85+
</div>
86+
</div>
87+
</div>
88+
</div>
89+
90+
<!-- Delete Confirmation Modal -->
91+
<div class="modal fade" id="delete-confirm-modal" tabindex="-1" aria-labelledby="delete-confirm-modal-label" aria-hidden="true">
92+
<div class="modal-dialog">
93+
<div class="modal-content">
94+
<div class="modal-header bg-danger text-white">
95+
<h5 class="modal-title" id="delete-confirm-modal-label"><i class="bi bi-exclamation-triangle me-2"></i>Confirm Delete</h5>
96+
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
97+
</div>
98+
<div class="modal-body">
99+
<p>Are you sure you want to delete the connection <strong id="delete-connection-name"></strong>?</p>
100+
<p class="text-muted mb-0">This action cannot be undone.</p>
101+
</div>
102+
<div class="modal-footer">
103+
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
104+
<button type="button" class="btn btn-danger" id="confirm-delete-btn">Delete</button>
105+
</div>
106+
</div>
107+
</div>
108+
</div>
109+
110+
<div id="toast-container" class="toast-container position-fixed end-0 p-3" style="z-index: 1060; bottom: 80px;"></div>
47111
<script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
48112
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
49113
<script src="https://cdn.jsdelivr.net/npm/bootstrap-select@1.14.0-beta3/dist/js/bootstrap-select.min.js"></script>

app/src/main/resources/ui/connection/connection.js

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
createInput,
99
createSelect,
1010
createToast,
11+
initToastHistoryListeners,
1112
syntaxHighlight
1213
} from "../shared.js";
1314
import {dataSourcePropertiesMap} from "../configuration-data.js";
@@ -79,27 +80,23 @@ async function testExistingConnection(connectionName, button) {
7980
});
8081

8182
const result = await response.json();
82-
// Limit to 100 characters, show ellipsis if truncated
83-
const resultDetails = result.details ? ` - ${truncateString(result.details, 100)}` : "";
8483
if (result.success) {
8584
createToast(
8685
`Test: ${connectionName}`,
87-
`${result.message}${resultDetails}`,
86+
`${result.message} ${result.details}`,
8887
"success"
8988
);
9089
} else {
9190
createToast(
9291
`Test: ${connectionName}`,
93-
`${result.message}${resultDetails}`,
92+
`${result.message} ${result.details}`,
9493
"fail"
9594
);
9695
}
9796
} catch (err) {
98-
// Limit to 100 characters, show ellipsis if truncated
99-
const resultDetails = err.message ? ` - ${truncateString(err.message, 100)}` : "";
10097
createToast(
10198
`Test: ${connectionName}`,
102-
`Connection test failed: ${resultDetails}`,
99+
`Connection test failed: ${err.message}`,
103100
"fail"
104101
);
105102
} finally {
@@ -108,10 +105,6 @@ async function testExistingConnection(connectionName, button) {
108105
}
109106
}
110107

111-
function truncateString(str, maxLength) {
112-
return str.length > maxLength ? str.substring(0, maxLength) + "..." : str;
113-
}
114-
115108
/**
116109
* Build a connection object from a data source container element
117110
* @param {HTMLElement} container - The data source container element
@@ -148,6 +141,7 @@ let numDataSources = 1;
148141
let numExistingConnections = 0;
149142

150143
dataSourceConfigRow.append(createDataSourceElement(numDataSources));
144+
initToastHistoryListeners();
151145
addDataSourceButton.addEventListener("click", function () {
152146
numDataSources += 1;
153147
let divider = document.createElement("hr");
@@ -261,19 +255,9 @@ async function getExistingConnections() {
261255
createToast(`${connection.name}`, `Cannot delete default connection. Modify application.conf or environment variables to remove.`, "fail");
262256
return;
263257
}
264-
265-
try {
266-
const response = await apiFetch(`/connection/${connection.name}`, {method: "DELETE"});
267-
if (response.ok) {
268-
accordionConnections.removeChild(accordionItem);
269-
createToast(`${connection.name}`, `Connection ${connection.name} deleted!`, "success");
270-
} else {
271-
const errorText = await response.text();
272-
createToast(`${connection.name}`, `Failed to delete connection: ${errorText}`, "fail");
273-
}
274-
} catch (err) {
275-
createToast(`${connection.name}`, `Failed to delete connection: ${err.message}`, "fail");
276-
}
258+
259+
// Show confirmation modal
260+
showDeleteConfirmation(connection.name, accordionItem);
277261
});
278262

279263
let buttonGroup = createButtonGroup(testButton, deleteButton);
@@ -376,3 +360,48 @@ function createOptGroup(label) {
376360
metadataSourceGroup.setAttribute("label", label);
377361
return metadataSourceGroup;
378362
}
363+
364+
// Delete confirmation modal handling
365+
let pendingDeleteConnection = null;
366+
let pendingDeleteAccordionItem = null;
367+
368+
function showDeleteConfirmation(connectionName, accordionItem) {
369+
pendingDeleteConnection = connectionName;
370+
pendingDeleteAccordionItem = accordionItem;
371+
372+
const nameElement = document.getElementById("delete-connection-name");
373+
if (nameElement) {
374+
nameElement.textContent = connectionName;
375+
}
376+
377+
const modal = new bootstrap.Modal(document.getElementById("delete-confirm-modal"));
378+
modal.show();
379+
}
380+
381+
// Set up the confirm delete button handler
382+
document.getElementById("confirm-delete-btn")?.addEventListener("click", async function () {
383+
if (!pendingDeleteConnection) return;
384+
385+
const connectionName = pendingDeleteConnection;
386+
const accordionItem = pendingDeleteAccordionItem;
387+
388+
// Close the modal
389+
const modal = bootstrap.Modal.getInstance(document.getElementById("delete-confirm-modal"));
390+
modal.hide();
391+
392+
try {
393+
const response = await apiFetch(`/connection/${connectionName}`, {method: "DELETE"});
394+
if (response.ok) {
395+
accordionConnections.removeChild(accordionItem);
396+
createToast(`${connectionName}`, `Connection ${connectionName} deleted!`, "success");
397+
} else {
398+
const errorText = await response.text();
399+
createToast(`${connectionName}`, `Failed to delete connection: ${errorText}`, "fail");
400+
}
401+
} catch (err) {
402+
createToast(`${connectionName}`, `Failed to delete connection: ${err.message}`, "fail");
403+
} finally {
404+
pendingDeleteConnection = null;
405+
pendingDeleteAccordionItem = null;
406+
}
407+
});

0 commit comments

Comments
 (0)