From a976ea635224bd511e665203fe5298a145f7d8d0 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Fri, 13 Nov 2026 17:11:26 +1000 Subject: [PATCH 1/3] fix: draw analytics charts in inter and fetch their data without waiting for chart.js (#5033) - start the loaddata fetches as soon as the inline script runs, and render the stat cards and tables as their data arrives - draw the charts once chart.js has loaded (chartjsready); if it fails to load, only the charts are missing - draw charts in inter straight away when it is already loaded, otherwise draw them in the fallback stack and switch chart.defaults.font.family to --fui-font-family once the inter stylesheet and face have loaded, so chart.js measures the labels again - split the event trend chart drawing out of rendereventcounts - re-render gh-pages anvil-explorer, hca-explorer and lungmap with their existing data, and bump the analytics package to 5.1.4 Co-Authored-By: Claude Opus 5.5 Co-authored-by: cc-claude-tools --- .../analytics/static_site/template/index.html | 106 ++++++++++++++---- analytics/analytics_package/pyproject.toml | 2 +- analytics/uv.lock | 2 +- gh-pages/anvil-explorer/index.html | 106 ++++++++++++++---- gh-pages/hca-explorer/index.html | 106 ++++++++++++++---- gh-pages/lungmap/index.html | 106 ++++++++++++++---- 6 files changed, 342 insertions(+), 86 deletions(-) diff --git a/analytics/analytics_package/analytics/static_site/template/index.html b/analytics/analytics_package/analytics/static_site/template/index.html index 61745327f..d0336b8ce 100644 --- a/analytics/analytics_package/analytics/static_site/template/index.html +++ b/analytics/analytics_package/analytics/static_site/template/index.html @@ -354,9 +354,14 @@

Filter Selections

diff --git a/analytics/analytics_package/pyproject.toml b/analytics/analytics_package/pyproject.toml index fc927e6b9..cd2f53782 100644 --- a/analytics/analytics_package/pyproject.toml +++ b/analytics/analytics_package/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "analytics" -version = "5.1.3" +version = "5.1.4" requires-python = ">=3.12" dependencies = [ "pandas>=3,<4", diff --git a/analytics/uv.lock b/analytics/uv.lock index c91e9dfb4..359b500df 100644 --- a/analytics/uv.lock +++ b/analytics/uv.lock @@ -18,7 +18,7 @@ members = [ [[package]] name = "analytics" -version = "5.1.3" +version = "5.1.4" source = { editable = "analytics_package" } dependencies = [ { name = "google-api-python-client" }, diff --git a/gh-pages/anvil-explorer/index.html b/gh-pages/anvil-explorer/index.html index 2007e8213..43be5090f 100644 --- a/gh-pages/anvil-explorer/index.html +++ b/gh-pages/anvil-explorer/index.html @@ -355,9 +355,14 @@

Filter Selections

diff --git a/gh-pages/hca-explorer/index.html b/gh-pages/hca-explorer/index.html index c2067cc6d..d9964f960 100644 --- a/gh-pages/hca-explorer/index.html +++ b/gh-pages/hca-explorer/index.html @@ -354,9 +354,14 @@

Filter Selections

diff --git a/gh-pages/lungmap/index.html b/gh-pages/lungmap/index.html index 46a188310..2d7b717a0 100644 --- a/gh-pages/lungmap/index.html +++ b/gh-pages/lungmap/index.html @@ -354,9 +354,14 @@

Filter Selections

From 60a862bc3de38e75d3747d8930da0d58e41bab05 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Fri, 13 Nov 2026 17:16:36 +1000 Subject: [PATCH 2/3] fix: keep the fallback font in analytics charts when the inter font file fails to load (#5033) - handle the document.fonts.load rejection (for example a csp that allows fonts.googleapis.com but blocks fonts.gstatic.com) so it isn't an unhandled promise rejection; errors from useinterincharts still surface - re-render gh-pages anvil-explorer, hca-explorer and lungmap Co-Authored-By: Claude Opus 5.5 Co-authored-by: cc-claude-tools --- .../analytics/static_site/template/index.html | 5 ++++- gh-pages/anvil-explorer/index.html | 5 ++++- gh-pages/hca-explorer/index.html | 5 ++++- gh-pages/lungmap/index.html | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/analytics/analytics_package/analytics/static_site/template/index.html b/analytics/analytics_package/analytics/static_site/template/index.html index d0336b8ce..619728c98 100644 --- a/analytics/analytics_package/analytics/static_site/template/index.html +++ b/analytics/analytics_package/analytics/static_site/template/index.html @@ -1174,7 +1174,10 @@

${escapeHtml(event.label)} } else { interStylesheetLoaded() .then(() => document.fonts.load('400 12px Inter')) - .then(useInterInCharts); + // On rejection the font file failed to load (a network + // failure, already in the console): the charts keep + // the fallback font, like the page's text. + .then(useInterInCharts, () => {}); } resolve(); }); diff --git a/gh-pages/anvil-explorer/index.html b/gh-pages/anvil-explorer/index.html index 43be5090f..f43dc9581 100644 --- a/gh-pages/anvil-explorer/index.html +++ b/gh-pages/anvil-explorer/index.html @@ -1175,7 +1175,10 @@

${escapeHtml(event.label)} } else { interStylesheetLoaded() .then(() => document.fonts.load('400 12px Inter')) - .then(useInterInCharts); + // On rejection the font file failed to load (a network + // failure, already in the console): the charts keep + // the fallback font, like the page's text. + .then(useInterInCharts, () => {}); } resolve(); }); diff --git a/gh-pages/hca-explorer/index.html b/gh-pages/hca-explorer/index.html index d9964f960..7015bc338 100644 --- a/gh-pages/hca-explorer/index.html +++ b/gh-pages/hca-explorer/index.html @@ -1174,7 +1174,10 @@

${escapeHtml(event.label)} } else { interStylesheetLoaded() .then(() => document.fonts.load('400 12px Inter')) - .then(useInterInCharts); + // On rejection the font file failed to load (a network + // failure, already in the console): the charts keep + // the fallback font, like the page's text. + .then(useInterInCharts, () => {}); } resolve(); }); diff --git a/gh-pages/lungmap/index.html b/gh-pages/lungmap/index.html index 2d7b717a0..d7b890346 100644 --- a/gh-pages/lungmap/index.html +++ b/gh-pages/lungmap/index.html @@ -1174,7 +1174,10 @@

${escapeHtml(event.label)} } else { interStylesheetLoaded() .then(() => document.fonts.load('400 12px Inter')) - .then(useInterInCharts); + // On rejection the font file failed to load (a network + // failure, already in the console): the charts keep + // the fallback font, like the page's text. + .then(useInterInCharts, () => {}); } resolve(); }); From eef561aaf50527ecf87cd32e45ec02b3740007c8 Mon Sep 17 00:00:00 2001 From: Fran McDade <18710366+frano-m@users.noreply.github.com> Date: Fri, 13 Nov 2026 17:23:05 +1000 Subject: [PATCH 3/3] fix: report a chart.js load failure once in the analytics pages (#5033) - attach the chart drawing chain at startup, waiting on chartjsready and a chartdataready promise that loaddata resolves, so a chart.js failure that lands before the data is no longer reported twice - re-render gh-pages anvil-explorer, hca-explorer and lungmap Co-Authored-By: Claude Opus 5.5 Co-authored-by: cc-claude-tools --- .../analytics/static_site/template/index.html | 24 +++++++++++++------ gh-pages/anvil-explorer/index.html | 24 +++++++++++++------ gh-pages/hca-explorer/index.html | 24 +++++++++++++------ gh-pages/lungmap/index.html | 24 +++++++++++++------ 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/analytics/analytics_package/analytics/static_site/template/index.html b/analytics/analytics_package/analytics/static_site/template/index.html index 619728c98..332c73d77 100644 --- a/analytics/analytics_package/analytics/static_site/template/index.html +++ b/analytics/analytics_package/analytics/static_site/template/index.html @@ -552,13 +552,7 @@

Filter Selections

renderFileDownloadEvents(fileDownloadEvents); const chartDataByKey = eventChartDataByKey(eventCharts); renderEventCounts(config, events, chartDataByKey); - // Not awaited, so if Chart.js fails to load only the charts - // are missing, rather than the error message replacing the report. - chartJsReady.then(() => { - renderTrafficChart(chronological); - renderPageviewsChart(chronological); - renderEventTrendCharts(chartDataByKey); - }); + resolveChartData({ chronological, chartDataByKey }); await renderEventStats(events, config); } catch (error) { console.error('Error loading data:', error); @@ -1183,6 +1177,22 @@

${escapeHtml(event.label)} }); }); + // Resolved by loadData with the charts' data. Never settles if the + // data fails to load, which loadData reports on the page. + let resolveChartData; + const chartDataReady = new Promise((resolve) => { resolveChartData = resolve; }); + + // Draws the charts once Chart.js and their data are both ready. Kept + // out of loadData so a Chart.js failure leaves only the charts + // missing, and attached now so that failure is reported once. + chartJsReady + .then(() => chartDataReady) + .then(({ chronological, chartDataByKey }) => { + renderTrafficChart(chronological); + renderPageviewsChart(chronological); + renderEventTrendCharts(chartDataByKey); + }); + // Starts the data fetches now, alongside the Chart.js download. loadData(); diff --git a/gh-pages/anvil-explorer/index.html b/gh-pages/anvil-explorer/index.html index f43dc9581..995cfb3c5 100644 --- a/gh-pages/anvil-explorer/index.html +++ b/gh-pages/anvil-explorer/index.html @@ -553,13 +553,7 @@

Filter Selections

renderFileDownloadEvents(fileDownloadEvents); const chartDataByKey = eventChartDataByKey(eventCharts); renderEventCounts(config, events, chartDataByKey); - // Not awaited, so if Chart.js fails to load only the charts - // are missing, rather than the error message replacing the report. - chartJsReady.then(() => { - renderTrafficChart(chronological); - renderPageviewsChart(chronological); - renderEventTrendCharts(chartDataByKey); - }); + resolveChartData({ chronological, chartDataByKey }); await renderEventStats(events, config); } catch (error) { console.error('Error loading data:', error); @@ -1184,6 +1178,22 @@

${escapeHtml(event.label)} }); }); + // Resolved by loadData with the charts' data. Never settles if the + // data fails to load, which loadData reports on the page. + let resolveChartData; + const chartDataReady = new Promise((resolve) => { resolveChartData = resolve; }); + + // Draws the charts once Chart.js and their data are both ready. Kept + // out of loadData so a Chart.js failure leaves only the charts + // missing, and attached now so that failure is reported once. + chartJsReady + .then(() => chartDataReady) + .then(({ chronological, chartDataByKey }) => { + renderTrafficChart(chronological); + renderPageviewsChart(chronological); + renderEventTrendCharts(chartDataByKey); + }); + // Starts the data fetches now, alongside the Chart.js download. loadData(); diff --git a/gh-pages/hca-explorer/index.html b/gh-pages/hca-explorer/index.html index 7015bc338..7ec665ea6 100644 --- a/gh-pages/hca-explorer/index.html +++ b/gh-pages/hca-explorer/index.html @@ -552,13 +552,7 @@

Filter Selections

renderFileDownloadEvents(fileDownloadEvents); const chartDataByKey = eventChartDataByKey(eventCharts); renderEventCounts(config, events, chartDataByKey); - // Not awaited, so if Chart.js fails to load only the charts - // are missing, rather than the error message replacing the report. - chartJsReady.then(() => { - renderTrafficChart(chronological); - renderPageviewsChart(chronological); - renderEventTrendCharts(chartDataByKey); - }); + resolveChartData({ chronological, chartDataByKey }); await renderEventStats(events, config); } catch (error) { console.error('Error loading data:', error); @@ -1183,6 +1177,22 @@

${escapeHtml(event.label)} }); }); + // Resolved by loadData with the charts' data. Never settles if the + // data fails to load, which loadData reports on the page. + let resolveChartData; + const chartDataReady = new Promise((resolve) => { resolveChartData = resolve; }); + + // Draws the charts once Chart.js and their data are both ready. Kept + // out of loadData so a Chart.js failure leaves only the charts + // missing, and attached now so that failure is reported once. + chartJsReady + .then(() => chartDataReady) + .then(({ chronological, chartDataByKey }) => { + renderTrafficChart(chronological); + renderPageviewsChart(chronological); + renderEventTrendCharts(chartDataByKey); + }); + // Starts the data fetches now, alongside the Chart.js download. loadData(); diff --git a/gh-pages/lungmap/index.html b/gh-pages/lungmap/index.html index d7b890346..9839aa053 100644 --- a/gh-pages/lungmap/index.html +++ b/gh-pages/lungmap/index.html @@ -552,13 +552,7 @@

Filter Selections

renderFileDownloadEvents(fileDownloadEvents); const chartDataByKey = eventChartDataByKey(eventCharts); renderEventCounts(config, events, chartDataByKey); - // Not awaited, so if Chart.js fails to load only the charts - // are missing, rather than the error message replacing the report. - chartJsReady.then(() => { - renderTrafficChart(chronological); - renderPageviewsChart(chronological); - renderEventTrendCharts(chartDataByKey); - }); + resolveChartData({ chronological, chartDataByKey }); await renderEventStats(events, config); } catch (error) { console.error('Error loading data:', error); @@ -1183,6 +1177,22 @@

${escapeHtml(event.label)} }); }); + // Resolved by loadData with the charts' data. Never settles if the + // data fails to load, which loadData reports on the page. + let resolveChartData; + const chartDataReady = new Promise((resolve) => { resolveChartData = resolve; }); + + // Draws the charts once Chart.js and their data are both ready. Kept + // out of loadData so a Chart.js failure leaves only the charts + // missing, and attached now so that failure is reported once. + chartJsReady + .then(() => chartDataReady) + .then(({ chronological, chartDataByKey }) => { + renderTrafficChart(chronological); + renderPageviewsChart(chronological); + renderEventTrendCharts(chartDataByKey); + }); + // Starts the data fetches now, alongside the Chart.js download. loadData();