mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-20 22:40:31 -04:00
Compare commits
10 Commits
83672eff4f
...
1669-toggl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86d25bcf7c | ||
|
|
43114494ec | ||
|
|
42d11315be | ||
|
|
c94c3de9f0 | ||
|
|
b01ca6bb15 | ||
|
|
6cecd5903a | ||
|
|
6e3ad6b354 | ||
|
|
d6079a463d | ||
|
|
7ac6400827 | ||
|
|
42db905026 |
@@ -238,17 +238,28 @@
|
||||
|
||||
// Update ARIA live region
|
||||
const liveRegion = document.getElementById('aria-live-account');
|
||||
liveRegion.textContent = `Data updated for ${selectedText} - Last 7 Days`;
|
||||
};
|
||||
liveRegion.textContent = `Data updated for ${selectedText} - Last 7 Days`;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize activityChart chart and table with service data by default
|
||||
fetchData('service');
|
||||
|
||||
// Add event listener to the dropdown
|
||||
const dropdown = document.getElementById('options');
|
||||
dropdown.addEventListener('change', handleDropdownChange);
|
||||
// Switch tables based on dropdown selection
|
||||
const selectedTable = selectedValue === "individual" ? "table1" : "table2";
|
||||
const tables = document.querySelectorAll('.table-overflow-x-auto');
|
||||
tables.forEach(function(table) {
|
||||
table.classList.add('hidden'); // Hide all tables by adding the hidden class
|
||||
table.classList.remove('visible'); // Ensure they are not visible
|
||||
});
|
||||
const tableToShow = document.getElementById(selectedTable);
|
||||
tableToShow.classList.remove('hidden'); // Remove hidden class
|
||||
tableToShow.classList.add('visible'); // Add visible class
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize activityChart chart and table with service data by default
|
||||
fetchData('service');
|
||||
|
||||
// Add event listener to the dropdown
|
||||
const dropdown = document.getElementById('options');
|
||||
dropdown.addEventListener('change', handleDropdownChange);
|
||||
});
|
||||
|
||||
// Resize chart on window resize
|
||||
window.addEventListener('resize', function() {
|
||||
|
||||
@@ -332,6 +332,12 @@ td.table-empty-message {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.table-overflow-x-auto {
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: units('desktop-lg')) {
|
||||
.table-overflow-x-auto {
|
||||
overflow-x: auto;
|
||||
|
||||
@@ -54,35 +54,72 @@
|
||||
|
||||
{% if current_user.has_permissions('manage_service') %}{% endif %}
|
||||
|
||||
{{ ajax_block(partials, updates_url, 'template-statistics') }}
|
||||
<h2 class="margin-top-4 margin-bottom-1">Recent Batches</h2>
|
||||
<div class="table-overflow-x-auto">
|
||||
<table class="usa-table usa-table--borderless job-table">
|
||||
<thead class="table-field-headings">
|
||||
<tr>
|
||||
<th scope="col" class="table-field-heading-first">
|
||||
<span>File name</span>
|
||||
</th>
|
||||
<th scope="col" class="table-field-heading">
|
||||
<span>Template</span>
|
||||
</th>
|
||||
<th scope="col" class="table-field-heading">
|
||||
<span>Job status</span>
|
||||
</th>
|
||||
<th scope="col" class="table-field-heading">
|
||||
<span>Sender</span>
|
||||
</th>
|
||||
<th scope="col" class="table-field-heading">
|
||||
<span># of Recipients</span>
|
||||
</th>
|
||||
<th scope="col" class="table-field-heading">
|
||||
<span>Report</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if jobs %}
|
||||
{% for job in jobs[:5] %}
|
||||
<div class="table-container">
|
||||
<div id="table1" class="table-overflow-x-auto hidden">
|
||||
<h2 class="margin-top-4 margin-bottom-1">My Activity</h2>
|
||||
<table class="usa-table job-table">
|
||||
<thead class="table-field-headings">
|
||||
<tr>
|
||||
<th scope="col" class="table-field-heading-first"><span>File name</span></th>
|
||||
<th scope="col" class="table-field-heading"><span>Template</span></th>
|
||||
<th scope="col" class="table-field-heading"><span>Job status</span></th>
|
||||
<th scope="col" class="table-field-heading"><span># of Recipients</span></th>
|
||||
<th scope="col" class="table-field-heading"><span>Report</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if jobs %}
|
||||
{% for job in jobs[:5] %}
|
||||
{% if job.created_by.name == current_user.name %}
|
||||
{% set notification = job.notifications[0] %}
|
||||
<tr id="{{ job.job_id }}">
|
||||
<td class="table-field file-name">
|
||||
{{ job.original_file_name[:12] if job.original_file_name else 'Manually entered number'}}
|
||||
<br>
|
||||
<a class="usa-link file-list-filename" href="{{ job.view_job_link }}">View Batch</a>
|
||||
</td>
|
||||
<td class="table-field template">{{ job.template_name }}</td>
|
||||
<td class="table-field time-sent">
|
||||
{{ (job.processing_finished if job.processing_finished else job.processing_started
|
||||
if job.processing_started else job.created_at)|format_datetime_table }}
|
||||
</td>
|
||||
<td class="table-field count-of-recipients">{{ job.notification_count }}</td>
|
||||
<td class="table-field report">
|
||||
{% if job.time_left != "Data no longer available" %}
|
||||
<a class="usa-link file-list-filename" href="{{ job.download_link }}">Download</a>
|
||||
<span class="usa-hint">{{ job.time_left }}</span>
|
||||
{% elif job %}
|
||||
<span>{{ job.time_left }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr class="table-row">
|
||||
<td class="table-empty-message" colspan="10">No batched job messages found  (messages are kept for {{ service_data_retention_days }} days).</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="table2" class="table-overflow-x-auto visible">
|
||||
<h2 class="margin-top-4 margin-bottom-1">All Activity</h2>
|
||||
<table class="usa-table job-table">
|
||||
<thead class="table-field-headings">
|
||||
<tr>
|
||||
<th scope="col" class="table-field-heading-first"><span>File name</span></th>
|
||||
<th scope="col" class="table-field-heading"><span>Template</span></th>
|
||||
<th scope="col" class="table-field-heading"><span>Job status</span></th>
|
||||
<th scope="col" class="table-field-heading"><span>Sender</span></th>
|
||||
<th scope="col" class="table-field-heading"><span># of Recipients</span></th>
|
||||
<th scope="col" class="table-field-heading"><span>Report</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if jobs %}
|
||||
{% for job in jobs[:5] %}
|
||||
{% set notification = job.notifications[0] %}
|
||||
<tr id="{{ job.job_id }}">
|
||||
<td class="table-field file-name">
|
||||
@@ -90,19 +127,13 @@
|
||||
<br>
|
||||
<a class="usa-link file-list-filename" href="{{ job.view_job_link }}">View Batch</a>
|
||||
</td>
|
||||
<td class="table-field template">
|
||||
{{ job.template_name }}
|
||||
</td>
|
||||
<td class="table-field template">{{ job.template_name }}</td>
|
||||
<td class="table-field time-sent">
|
||||
{{ (job.processing_finished if job.processing_finished else job.processing_started
|
||||
if job.processing_started else job.created_at)|format_datetime_table }}
|
||||
</td>
|
||||
<td class="table-field sender">
|
||||
{{ job.created_by.name }}
|
||||
</td>
|
||||
<td class="table-field count-of-recipients">
|
||||
{{ job.notification_count}}
|
||||
</td>
|
||||
<td class="table-field sender">{{ job.created_by.name }}</td>
|
||||
<td class="table-field count-of-recipients">{{ job.notification_count }}</td>
|
||||
<td class="table-field report">
|
||||
{% if job.time_left != "Data no longer available" %}
|
||||
<a class="usa-link file-list-filename" href="{{ job.download_link }}">Download</a>
|
||||
@@ -112,17 +143,16 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr class="table-row">
|
||||
<td class="table-empty-message" colspan="10">No batched job messages found  (messages are kept for {{ service_data_retention_days }} days).</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr class="table-row">
|
||||
<td class="table-empty-message" colspan="10">No batched job messages found  (messages are kept for {{ service_data_retention_days }} days).</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Recent templates</h2>
|
||||
{{ ajax_block(partials, updates_url, 'template-statistics') }}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<div class="ajax-block-container">
|
||||
{% if template_statistics|length > 1 %}
|
||||
<h2 class="margin-top-4 margin-bottom-1">Recent Templates</h2>
|
||||
<div class='template-statistics-table table-overflow-x-auto'>
|
||||
{% call(item, row_number) list_table(
|
||||
template_statistics,
|
||||
|
||||
6
poetry.lock
generated
6
poetry.lock
generated
@@ -1325,9 +1325,13 @@ files = [
|
||||
{file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"},
|
||||
{file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"},
|
||||
@@ -1662,7 +1666,6 @@ files = [
|
||||
{file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"},
|
||||
{file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"},
|
||||
{file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"},
|
||||
{file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
|
||||
]
|
||||
|
||||
@@ -2509,6 +2512,7 @@ files = [
|
||||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
||||
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
||||
|
||||
@@ -595,18 +595,6 @@ def test_should_show_recent_templates_on_dashboard(
|
||||
]
|
||||
assert "Total Messages" in headers
|
||||
|
||||
table_rows = page.find_all("tbody")[0].find_all("tr")
|
||||
|
||||
assert len(table_rows) == 2
|
||||
|
||||
assert "two" in table_rows[0].find_all("td")[0].text
|
||||
assert "Email template" in table_rows[0].find_all("td")[0].text
|
||||
assert "200" in table_rows[0].find_all("td")[1].text
|
||||
|
||||
assert "one" in table_rows[1].find_all("td")[0].text
|
||||
assert "Text message template" in table_rows[1].find_all("td")[0].text
|
||||
assert "100" in table_rows[1].find_all("td")[1].text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"stats",
|
||||
@@ -1808,7 +1796,8 @@ def test_service_dashboard_shows_batched_jobs(
|
||||
|
||||
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
|
||||
|
||||
job_table_body = page.find("table", class_="job-table")
|
||||
wrapper_div = page.find("div", id="table2")
|
||||
job_table_body = wrapper_div.find("table", class_="job-table")
|
||||
|
||||
rows = job_table_body.find_all("tbody")[0].find_all("tr")
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
|
||||
});
|
||||
|
||||
// beforeAll hook to set up the DOM and load D3.js script
|
||||
beforeAll(done => {
|
||||
beforeEach(done => {
|
||||
// Set up the DOM with the D3 script included
|
||||
document.body.innerHTML = `
|
||||
<div id="activityChartContainer">
|
||||
@@ -40,8 +40,14 @@ beforeAll(done => {
|
||||
</div>
|
||||
</div>
|
||||
<div id="aria-live-account" class="usa-sr-only" aria-live="polite"></div>
|
||||
<div id="table1" class="table-overflow-x-auto hidden"></div>
|
||||
<div id="table2" class="table-overflow-x-auto visible"></div>
|
||||
`;
|
||||
|
||||
// Re-bind event listeners in case they are lost between tests
|
||||
const dropdown = document.getElementById('options');
|
||||
dropdown.addEventListener('change', window.handleDropdownChange);
|
||||
|
||||
// Load the D3 script dynamically
|
||||
loadScript(d3ScriptContent);
|
||||
|
||||
@@ -53,6 +59,11 @@ beforeAll(done => {
|
||||
}, 100);
|
||||
}, 10000); // Increased timeout to 10 seconds
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up DOM or restore mocks if needed
|
||||
jest.restoreAllMocks(); // Restores all mocks, including console logs
|
||||
});
|
||||
|
||||
test('D3 is loaded correctly', () => {
|
||||
// Check if D3 is loaded by verifying the existence of the d3 object
|
||||
expect(window.d3).toBeDefined();
|
||||
@@ -125,6 +136,92 @@ test('Check HTML content after chart creation', () => {
|
||||
expect(container.querySelectorAll('rect').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// Add toHaveTextContent matcher manually
|
||||
expect.extend({
|
||||
toHaveTextContent(received, expected) {
|
||||
const actualTextContent = received.textContent;
|
||||
const pass = actualTextContent.includes(expected);
|
||||
if (pass) {
|
||||
return {
|
||||
message: () =>
|
||||
`expected element not to have text content '${expected}', but it was found`,
|
||||
pass: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
message: () =>
|
||||
`expected element to have text content '${expected}', but got '${actualTextContent}'`,
|
||||
pass: false,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
test('Initial dropdown state is correct', () => {
|
||||
const dropdown = document.getElementById('options');
|
||||
|
||||
// Check the initial value of the dropdown
|
||||
expect(dropdown.value).toBe('service');
|
||||
|
||||
// Check the initial subtitle
|
||||
const subTitle = document.querySelector('.chart-subtitle');
|
||||
expect(subTitle).toHaveTextContent('Service Name - Last 7 Days');
|
||||
|
||||
// Check the initial ARIA live region text
|
||||
const liveRegion = document.getElementById('aria-live-account');
|
||||
expect(liveRegion.textContent).toBe(''); // Assuming it starts empty
|
||||
});
|
||||
|
||||
test('handleDropdownChange updates subtitle and fetches individual data', () => {
|
||||
// Get the dropdown and set its value to 'individual'
|
||||
const dropdown = document.getElementById('options');
|
||||
dropdown.value = 'individual';
|
||||
|
||||
// Create and dispatch a change event
|
||||
const changeEvent = new Event('change', { bubbles: true });
|
||||
dropdown.dispatchEvent(changeEvent);
|
||||
|
||||
// Check if subtitle text content is updated correctly
|
||||
const subTitle = document.querySelector('.chart-subtitle');
|
||||
expect(subTitle).toHaveTextContent('User Name - Last 7 Days');
|
||||
});
|
||||
|
||||
test('handleDropdownChange shows "My Activity" table and hides "All Activity" table', () => {
|
||||
// Get the dropdown and set its value to 'individual'
|
||||
const dropdown = document.getElementById('options');
|
||||
dropdown.value = 'individual';
|
||||
|
||||
// Create and dispatch a change event
|
||||
const changeEvent = new Event('change', { bubbles: true });
|
||||
dropdown.dispatchEvent(changeEvent);
|
||||
|
||||
// Check visibility of the tables
|
||||
const table1 = document.getElementById('table1');
|
||||
const table2 = document.getElementById('table2');
|
||||
|
||||
// Expect table1 to be visible and table2 to be hidden
|
||||
expect(table1.classList).not.toContain('hidden');
|
||||
expect(table1.classList).toContain('visible');
|
||||
expect(table2.classList).toContain('hidden');
|
||||
expect(table2.classList).not.toContain('visible');
|
||||
});
|
||||
|
||||
test('Dropdown change event triggers handleDropdownChange with logging', () => {
|
||||
// Spy on console.log
|
||||
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||
|
||||
// Get the dropdown and set its value
|
||||
const dropdown = document.getElementById('options');
|
||||
dropdown.value = 'individual';
|
||||
|
||||
// Create and dispatch a change event
|
||||
const changeEvent = new Event('change', { bubbles: true });
|
||||
dropdown.dispatchEvent(changeEvent);
|
||||
|
||||
// Restore the console method
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
|
||||
test('Initial fetch data populates chart and table', done => {
|
||||
const mockData = {
|
||||
|
||||
Reference in New Issue
Block a user