@@ -5,20 +5,18 @@ author: "Copernicus Land Monitoring Service"
55date : 2026-07-16
66category : non-browsable
77type : dashboard
8- execute :
9- echo : false
10- format :
11- html :
12- code-fold : false
138---
149
1510
1611
1712``` {ojs}
1813// ── Load data from the canonical data repo ──
1914data = await fetch("https://raw.githubusercontent.com/copernicus-land/clms-cdse-migration-data/main/migration_data.json")
20- .then(r => { if (!r.ok) throw new Error(String(r.status)); return r.json() })
21- .catch(e => { console.error("Failed to load data", e); return {groups:[], portfolio:{}, cdse:{}, summary:{}} })
15+ .then(r => { if (!r.ok) throw new Error("HTTP " + r.status); return r.json() })
16+ // On failure, return a COMPLETE empty shape (every field the cells below read)
17+ // plus `_loadError`, so nothing downstream throws and the dynamic blocks can
18+ // show an error section instead of crashing the OJS runtime.
19+ .catch(e => { console.error("Failed to load data", e); return {_loadError: String(e && e.message || e), groups:[], portfolio:{}, cdse:{}, summary:{}, scraped_at:"", odata_tracking:{history:[]}, previous:{groups:[]}} })
2220groups = data.groups
2321summary = data.summary
2422portfolio = data.portfolio
@@ -102,6 +100,14 @@ check = (on) => on
102100 ? htl.html`<span style="color:#00aa00;font-weight:bold">✓</span>`
103101 : htl.html`<span style="color:#ccc">✗</span>`
104102
103+ // ── Error banner shown in place of the dynamic blocks when the live data
104+ // load failed. A FUNCTION (not a plain cell) so Quarto doesn't auto-display it;
105+ // it's only rendered when a gated block calls errorSection(). ──
106+ errorSection = () => htl.html`<div style="border:1px solid #e0b4b4;background:#fff6f6;border-radius:6px;padding:16px;margin:12px 0;color:#912d2b">
107+ <strong>Dashboard data could not be loaded.</strong>
108+ <div style="margin-top:6px">The live data source did not respond${data._loadError ? htl.html` (${data._loadError})` : ""}. Please refresh the page or try again later.</div>
109+ </div>`
110+
105111// ── Deltas vs previous run, computed client-side from the embedded snapshot ──
106112prevByName = Object.fromEntries((data.previous?.groups || []).map(g => [g.name, g]))
107113changes = (data.previous?.groups?.length ? groups : []).map(g => {
@@ -251,7 +257,9 @@ onboardingPlot = {
251257## Migration Statistics
252258
253259``` {ojs}
254- html`
260+ {
261+ if (data._loadError) return errorSection();
262+ return html`
255263${newGroups.length > 0 ? htl.html `
256264<div style="background:#e8f5e9;border:1px solid #a5d6a7;border-radius:6px;padding:12px;margin:12px 0">
257265 <strong>🆕 Products ${lastWeek}</strong>
@@ -272,7 +280,8 @@ ${(odataWeeklyDelta || odataDailyDelta || odataHist.length > 0) ? htl.html `
272280 ${odataDailyAvg ? htl.html `<div style="margin:4px 0;color:#888;font-size:0.85em">Daily average: ${odataDailyAvg.toLocaleString()} new files/day</div>` : ""}
273281 ${onboardingPlot}
274282</div>` : ""}
275- `
283+ `;
284+ }
276285```
277286
278287## CLMS Product on CDSE
@@ -283,6 +292,7 @@ This table shows the full, currently active CLMS portfolio and each product's mi
283292// (multi-select, all enabled by default). Replaces the old single-select
284293// dropdown, which only let one component be viewed at a time.
285294viewof activeComponents = {
295+ if (data._loadError) { const el = document.createElement("span"); el.value = new Set(); return el; }
286296 const allComponents = [...new Set(groups.map(d => componentOf(d)).filter(Boolean))].sort()
287297 const active = new Set(allComponents)
288298
@@ -312,14 +322,17 @@ viewof activeComponents = {
312322 return container;
313323}
314324
315- viewof searchTerm = Inputs.text({label: "Search", placeholder: "Product group…", width: 250})
325+ viewof searchTerm = data._loadError
326+ ? Object.assign(document.createElement("span"), { value: "" })
327+ : Inputs.text({label: "Search", placeholder: "Product group…", width: 250})
316328```
317329
318330``` {ojs}
319331// ── Build the table via plain DOM APIs ──
320332// so that column widths defined on <colgroup> are guaranteed to apply to
321333// every row, including rows toggled between hidden/visible.
322334productGroupsTable = {
335+ if (data._loadError) return htl.html``;
323336 // Category takes the remaining width (~46%) now that Progress is gone
324337 const colWidths = ["auto", "9%", "9%", "9%", "9%", "9%", "9%"];
325338
0 commit comments