mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-24 00:07:02 -04:00
improve polling performance and remove unused updateContent module
This commit is contained in:
@@ -5,6 +5,9 @@
|
|||||||
const tableContainer = document.getElementById('activityContainer');
|
const tableContainer = document.getElementById('activityContainer');
|
||||||
const currentUserName = tableContainer.getAttribute('data-currentUserName');
|
const currentUserName = tableContainer.getAttribute('data-currentUserName');
|
||||||
const currentServiceId = tableContainer.getAttribute('data-currentServiceId');
|
const currentServiceId = tableContainer.getAttribute('data-currentServiceId');
|
||||||
|
let pollInterval;
|
||||||
|
let isPolling = false;
|
||||||
|
const POLL_INTERVAL_MS = 25000;
|
||||||
const COLORS = {
|
const COLORS = {
|
||||||
delivered: '#0076d6',
|
delivered: '#0076d6',
|
||||||
failed: '#fa9441',
|
failed: '#fa9441',
|
||||||
@@ -153,8 +156,6 @@
|
|||||||
.on('mouseout', function() {
|
.on('mouseout', function() {
|
||||||
tooltip.style('display', 'none');
|
tooltip.style('display', 'none');
|
||||||
})
|
})
|
||||||
.transition()
|
|
||||||
.duration(1000)
|
|
||||||
.attr('y', d => y(d[1]))
|
.attr('y', d => y(d[1]))
|
||||||
.attr('height', d => {
|
.attr('height', d => {
|
||||||
const calculatedHeight = y(d[0]) - y(d[1]);
|
const calculatedHeight = y(d[0]) - y(d[1]);
|
||||||
@@ -209,28 +210,35 @@
|
|||||||
table.append(tbody);
|
table.append(tbody);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchData = function(type) {
|
const fetchData = async function(type) {
|
||||||
|
if (isPolling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.hidden) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var ctx = document.getElementById('weeklyChart');
|
var ctx = document.getElementById('weeklyChart');
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPolling = true;
|
||||||
|
|
||||||
var userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
var userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
|
||||||
var url = type === 'service'
|
var url = type === 'service'
|
||||||
? `/services/${currentServiceId}/daily-stats.json?timezone=${encodeURIComponent(userTimezone)}`
|
? `/services/${currentServiceId}/daily-stats.json?timezone=${encodeURIComponent(userTimezone)}`
|
||||||
: `/services/${currentServiceId}/daily-stats-by-user.json?timezone=${encodeURIComponent(userTimezone)}`;
|
: `/services/${currentServiceId}/daily-stats-by-user.json?timezone=${encodeURIComponent(userTimezone)}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
|
||||||
return fetch(url)
|
const data = await response.json();
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
labels = [];
|
labels = [];
|
||||||
deliveredData = [];
|
deliveredData = [];
|
||||||
failedData = [];
|
failedData = [];
|
||||||
@@ -277,10 +285,38 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
})
|
} catch (error) {
|
||||||
.catch(error => console.error('Error fetching daily stats:', error));
|
console.error('Error fetching daily stats:', error);
|
||||||
};
|
} finally {
|
||||||
setInterval(() => fetchData(currentType), 25000);
|
isPolling = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function startPolling() {
|
||||||
|
fetchData(currentType);
|
||||||
|
|
||||||
|
pollInterval = setInterval(() => {
|
||||||
|
fetchData(currentType);
|
||||||
|
}, POLL_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopPolling() {
|
||||||
|
if (pollInterval) {
|
||||||
|
clearInterval(pollInterval);
|
||||||
|
pollInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
stopPolling();
|
||||||
|
} else {
|
||||||
|
stopPolling();
|
||||||
|
startPolling();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
startPolling();
|
||||||
const handleDropdownChange = function(event) {
|
const handleDropdownChange = function(event) {
|
||||||
const selectedValue = event.target.value;
|
const selectedValue = event.target.value;
|
||||||
currentType = selectedValue;
|
currentType = selectedValue;
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import Button from 'govuk-frontend/components/button/button';
|
|||||||
import Radios from 'govuk-frontend/components/radios/radios';
|
import Radios from 'govuk-frontend/components/radios/radios';
|
||||||
|
|
||||||
// Modules from 3rd party vendors
|
// Modules from 3rd party vendors
|
||||||
import morphdom from 'morphdom';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Ideally this would be a NodeList.prototype.forEach polyfill
|
* TODO: Ideally this would be a NodeList.prototype.forEach polyfill
|
||||||
@@ -67,13 +66,8 @@ var Frontend = {
|
|||||||
"initAll": initAll
|
"initAll": initAll
|
||||||
}
|
}
|
||||||
|
|
||||||
var vendor = {
|
|
||||||
"morphdom": morphdom
|
|
||||||
}
|
|
||||||
|
|
||||||
// The exported object will be assigned to window.GOVUK in our production code
|
// The exported object will be assigned to window.GOVUK in our production code
|
||||||
// (bundled into an IIFE by RollupJS)
|
// (bundled into an IIFE by RollupJS)
|
||||||
export {
|
export {
|
||||||
Frontend,
|
Frontend
|
||||||
vendor
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
function debounce(func, wait) {
|
|
||||||
let timeout;
|
|
||||||
return function (...args) {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const isJobPage = window.location.pathname.includes('/jobs/');
|
const isJobPage = window.location.pathname.includes('/jobs/');
|
||||||
if (!isJobPage) return;
|
if (!isJobPage) return;
|
||||||
@@ -15,69 +7,105 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
const featureEnabled = jobEl?.dataset?.feature === 'true';
|
const featureEnabled = jobEl?.dataset?.feature === 'true';
|
||||||
const apiHost = jobEl?.dataset?.host;
|
const apiHost = jobEl?.dataset?.host;
|
||||||
|
|
||||||
if (!jobId) return;
|
if (!jobId || !featureEnabled) return;
|
||||||
|
|
||||||
if (featureEnabled) {
|
const DEFAULT_INTERVAL_MS = 10000;
|
||||||
const socket = io(apiHost);
|
const MIN_INTERVAL_MS = 1000;
|
||||||
|
const MAX_INTERVAL_MS = 30000;
|
||||||
|
|
||||||
socket.on('connect_error', (err) => {
|
let pollInterval;
|
||||||
console.error('Socket connect_error:', err);
|
let currentInterval = DEFAULT_INTERVAL_MS;
|
||||||
});
|
let isPolling = false;
|
||||||
|
|
||||||
socket.on('error', (err) => {
|
function calculateBackoff(responseTime) {
|
||||||
console.error('Socket error:', err);
|
return Math.min(
|
||||||
});
|
MAX_INTERVAL_MS,
|
||||||
|
Math.max(
|
||||||
socket.on('connect', () => {
|
MIN_INTERVAL_MS,
|
||||||
socket.emit('join', { room: `job-${jobId}` });
|
Math.floor((250 * Math.sqrt(responseTime)) - 1000)
|
||||||
});
|
)
|
||||||
|
);
|
||||||
window.addEventListener('beforeunload', () => {
|
|
||||||
socket.emit('leave', { room: `job-${jobId}` });
|
|
||||||
});
|
|
||||||
|
|
||||||
const debouncedUpdate = debounce((data) => {
|
|
||||||
updateAllJobSections();
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
socket.on('job_updated', (data) => {
|
|
||||||
if (data.job_id !== jobId) return;
|
|
||||||
debouncedUpdate(data);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAllJobSections() {
|
async function updateAllJobSections() {
|
||||||
|
if (document.hidden || isPolling) return;
|
||||||
|
|
||||||
|
isPolling = true;
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
const resourceEl = document.querySelector('[data-socket-update="status"]');
|
const resourceEl = document.querySelector('[data-socket-update="status"]');
|
||||||
const url = resourceEl?.dataset?.resource;
|
const url = resourceEl?.dataset?.resource;
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
console.warn('No resource URL found for job updates');
|
isPolling = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(url)
|
try {
|
||||||
.then((res) => res.json())
|
const response = await fetch(url);
|
||||||
.then(({ status, counts, notifications }) => {
|
const data = await response.json();
|
||||||
const sections = {
|
|
||||||
status: document.querySelector('[data-socket-update="status"]'),
|
|
||||||
counts: document.querySelector('[data-socket-update="counts"]'),
|
|
||||||
notifications: document.querySelector(
|
|
||||||
'[data-socket-update="notifications"]'
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (status && sections.status) {
|
const sections = {
|
||||||
sections.status.innerHTML = status;
|
status: document.querySelector('[data-socket-update="status"]'),
|
||||||
}
|
counts: document.querySelector('[data-socket-update="counts"]'),
|
||||||
if (counts && sections.counts) {
|
notifications: document.querySelector('[data-socket-update="notifications"]'),
|
||||||
sections.counts.innerHTML = counts;
|
};
|
||||||
}
|
|
||||||
if (notifications && sections.notifications) {
|
if (data.status && sections.status) {
|
||||||
sections.notifications.innerHTML = notifications;
|
sections.status.innerHTML = data.status;
|
||||||
}
|
}
|
||||||
})
|
if (data.counts && sections.counts) {
|
||||||
.catch((err) => {
|
sections.counts.innerHTML = data.counts;
|
||||||
console.error('Error fetching job update partials:', err);
|
}
|
||||||
});
|
if (data.notifications && sections.notifications) {
|
||||||
|
sections.notifications.innerHTML = data.notifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseTime = Date.now() - startTime;
|
||||||
|
currentInterval = calculateBackoff(responseTime);
|
||||||
|
|
||||||
|
if (data.stop === 1 || data.finished === true) {
|
||||||
|
stopPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching job updates:', error);
|
||||||
|
currentInterval = Math.min(currentInterval * 2, MAX_INTERVAL_MS);
|
||||||
|
} finally {
|
||||||
|
isPolling = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startPolling() {
|
||||||
|
updateAllJobSections();
|
||||||
|
|
||||||
|
function scheduleNext() {
|
||||||
|
if (pollInterval) clearTimeout(pollInterval);
|
||||||
|
pollInterval = setTimeout(() => {
|
||||||
|
updateAllJobSections();
|
||||||
|
scheduleNext();
|
||||||
|
}, currentInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopPolling() {
|
||||||
|
if (pollInterval) {
|
||||||
|
clearTimeout(pollInterval);
|
||||||
|
pollInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
stopPolling();
|
||||||
|
} else {
|
||||||
|
startPolling();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', stopPolling);
|
||||||
|
|
||||||
|
startPolling();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,3 @@
|
|||||||
{% macro ajax_block(partials, url, key, finished=False, form='') %}
|
{% macro ajax_block(partials, url, key, finished=False, form='') %}
|
||||||
{% if not finished %}
|
{{ partials[key]|safe }}
|
||||||
<div
|
|
||||||
data-module="update-content"
|
|
||||||
data-resource="{{ url }}"
|
|
||||||
data-key="{{ key }}"
|
|
||||||
data-form="{{ form }}"
|
|
||||||
>
|
|
||||||
{% endif %}
|
|
||||||
{{ partials[key]|safe }}
|
|
||||||
{% if not finished %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
7
package-lock.json
generated
7
package-lock.json
generated
@@ -19,7 +19,6 @@
|
|||||||
"govuk-frontend": "2.13.0",
|
"govuk-frontend": "2.13.0",
|
||||||
"gulp-merge": "^0.1.1",
|
"gulp-merge": "^0.1.1",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"morphdom": "^2.7.7",
|
|
||||||
"playwright": "^1.55.0",
|
"playwright": "^1.55.0",
|
||||||
"python": "^0.0.4",
|
"python": "^0.0.4",
|
||||||
"query-command-supported": "1.0.0",
|
"query-command-supported": "1.0.0",
|
||||||
@@ -11204,12 +11203,6 @@
|
|||||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/morphdom": {
|
|
||||||
"version": "2.7.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.7.tgz",
|
|
||||||
"integrity": "sha512-04GmsiBcalrSCNmzfo+UjU8tt3PhZJKzcOy+r1FlGA7/zri8wre3I1WkYN9PT3sIeIKfW9bpyElA+VzOg2E24g==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
"govuk-frontend": "2.13.0",
|
"govuk-frontend": "2.13.0",
|
||||||
"gulp-merge": "^0.1.1",
|
"gulp-merge": "^0.1.1",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"morphdom": "^2.7.7",
|
|
||||||
"playwright": "^1.55.0",
|
"playwright": "^1.55.0",
|
||||||
"python": "^0.0.4",
|
"python": "^0.0.4",
|
||||||
"query-command-supported": "1.0.0",
|
"query-command-supported": "1.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user