improve polling performance and remove unused updateContent module

This commit is contained in:
Beverly Nguyen
2025-09-15 13:16:13 -07:00
parent 5ae6cf7abf
commit f765f19c3b
6 changed files with 140 additions and 101 deletions

View File

@@ -5,6 +5,9 @@
const tableContainer = document.getElementById('activityContainer');
const currentUserName = tableContainer.getAttribute('data-currentUserName');
const currentServiceId = tableContainer.getAttribute('data-currentServiceId');
let pollInterval;
let isPolling = false;
const POLL_INTERVAL_MS = 25000;
const COLORS = {
delivered: '#0076d6',
failed: '#fa9441',
@@ -153,8 +156,6 @@
.on('mouseout', function() {
tooltip.style('display', 'none');
})
.transition()
.duration(1000)
.attr('y', d => y(d[1]))
.attr('height', d => {
const calculatedHeight = y(d[0]) - y(d[1]);
@@ -209,28 +210,35 @@
table.append(tbody);
};
const fetchData = function(type) {
const fetchData = async function(type) {
if (isPolling) {
return;
}
if (document.hidden) {
return;
}
var ctx = document.getElementById('weeklyChart');
if (!ctx) {
return;
}
isPolling = true;
var userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
var url = type === 'service'
? `/services/${currentServiceId}/daily-stats.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)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
const data = await response.json();
labels = [];
deliveredData = [];
failedData = [];
@@ -277,10 +285,38 @@
}
return data;
})
.catch(error => console.error('Error fetching daily stats:', error));
};
setInterval(() => fetchData(currentType), 25000);
} catch (error) {
console.error('Error fetching daily stats:', error);
} finally {
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 selectedValue = event.target.value;
currentType = selectedValue;

View File

@@ -14,7 +14,6 @@ import Button from 'govuk-frontend/components/button/button';
import Radios from 'govuk-frontend/components/radios/radios';
// Modules from 3rd party vendors
import morphdom from 'morphdom';
/**
* TODO: Ideally this would be a NodeList.prototype.forEach polyfill
@@ -67,13 +66,8 @@ var Frontend = {
"initAll": initAll
}
var vendor = {
"morphdom": morphdom
}
// The exported object will be assigned to window.GOVUK in our production code
// (bundled into an IIFE by RollupJS)
export {
Frontend,
vendor
Frontend
}

View File

@@ -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 () {
const isJobPage = window.location.pathname.includes('/jobs/');
if (!isJobPage) return;
@@ -15,69 +7,105 @@ document.addEventListener('DOMContentLoaded', function () {
const featureEnabled = jobEl?.dataset?.feature === 'true';
const apiHost = jobEl?.dataset?.host;
if (!jobId) return;
if (!jobId || !featureEnabled) return;
if (featureEnabled) {
const socket = io(apiHost);
const DEFAULT_INTERVAL_MS = 10000;
const MIN_INTERVAL_MS = 1000;
const MAX_INTERVAL_MS = 30000;
socket.on('connect_error', (err) => {
console.error('Socket connect_error:', err);
});
let pollInterval;
let currentInterval = DEFAULT_INTERVAL_MS;
let isPolling = false;
socket.on('error', (err) => {
console.error('Socket error:', err);
});
socket.on('connect', () => {
socket.emit('join', { room: `job-${jobId}` });
});
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 calculateBackoff(responseTime) {
return Math.min(
MAX_INTERVAL_MS,
Math.max(
MIN_INTERVAL_MS,
Math.floor((250 * Math.sqrt(responseTime)) - 1000)
)
);
}
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 url = resourceEl?.dataset?.resource;
if (!url) {
console.warn('No resource URL found for job updates');
isPolling = false;
return;
}
fetch(url)
.then((res) => res.json())
.then(({ status, counts, notifications }) => {
const sections = {
status: document.querySelector('[data-socket-update="status"]'),
counts: document.querySelector('[data-socket-update="counts"]'),
notifications: document.querySelector(
'[data-socket-update="notifications"]'
),
};
try {
const response = await fetch(url);
const data = await response.json();
if (status && sections.status) {
sections.status.innerHTML = status;
}
if (counts && sections.counts) {
sections.counts.innerHTML = counts;
}
if (notifications && sections.notifications) {
sections.notifications.innerHTML = notifications;
}
})
.catch((err) => {
console.error('Error fetching job update partials:', err);
});
const sections = {
status: document.querySelector('[data-socket-update="status"]'),
counts: document.querySelector('[data-socket-update="counts"]'),
notifications: document.querySelector('[data-socket-update="notifications"]'),
};
if (data.status && sections.status) {
sections.status.innerHTML = data.status;
}
if (data.counts && sections.counts) {
sections.counts.innerHTML = data.counts;
}
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();
});

View File

@@ -1,14 +1,3 @@
{% macro ajax_block(partials, url, key, finished=False, form='') %}
{% if not finished %}
<div
data-module="update-content"
data-resource="{{ url }}"
data-key="{{ key }}"
data-form="{{ form }}"
>
{% endif %}
{{ partials[key]|safe }}
{% if not finished %}
</div>
{% endif %}
{{ partials[key]|safe }}
{% endmacro %}

7
package-lock.json generated
View File

@@ -19,7 +19,6 @@
"govuk-frontend": "2.13.0",
"gulp-merge": "^0.1.1",
"jquery": "3.7.1",
"morphdom": "^2.7.7",
"playwright": "^1.55.0",
"python": "^0.0.4",
"query-command-supported": "1.0.0",
@@ -11204,12 +11203,6 @@
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"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": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View File

@@ -35,7 +35,6 @@
"govuk-frontend": "2.13.0",
"gulp-merge": "^0.1.1",
"jquery": "3.7.1",
"morphdom": "^2.7.7",
"playwright": "^1.55.0",
"python": "^0.0.4",
"query-command-supported": "1.0.0",