/* ============================================================ TDS Errors view — container & fetch orchestration ------------------------------------------------------------ IA (v2 — reworked after design review): 1. Range + filter toolbar — URL-hash-shareable state. 2. Env comparison cards — per-env loading/error/empty states; one env failing (UAT on heavy windows) degrades gracefully. 3. Availability incidents — gantt timeline from snapshots. Ongoing outages surface here (pulsing bar pinned to top) and on the env cards. 4. Failing backends — backend × environment matrix (upstream systems vs UAT TRG INT SUP), expandable into error categories, per-row trend sparklines. Cell click → drill drawer with grouped patterns and grep-able raw rows. Fetching: mapLimitSettled cap 2; per-(env,range) cache; NO auto-refresh — this is a forensic view, and refresh cycles stacked requests behind hung aggregate calls in production. Re-fetch happens on range/filter change or per-env Retry. ============================================================ */ const { useState: vUseState, useEffect: vUseEffect, useMemo: vUseMemo, useCallback: vUseCallback } = React; const EV_API = window.TDS_ERRORS_API; function evDefaultFilters(params) { return { envs: (params.envs ? params.envs.split(',') : EV_API.ENVS).filter((e) => EV_API.ENVS.includes(e)), rangeId: params.range || '7d', customFrom: params.from || '', customTo: params.to || '', feature: params.feature || '', step: params.step || '', team: params.team || '', brand: params.brand || '', }; } function evParseDrill(params) { // bk=ENV:BACKEND[:CATEGORY] — shareable drill-down if (!params.bk) return null; const [env, backendId, category] = params.bk.split(':'); if (!EV_API.ENVS.includes(env) || !EV_BACKENDS.some((b) => b.id === backendId)) return null; return { env, backendId, category: category || null }; } function ErrorsView() { const initialParams = vUseMemo(() => EV_API.parseHash().params, []); const [filters, setFilters] = vUseState(() => evDefaultFilters(initialParams)); const [envState, setEnvState] = vUseState({}); // aggregate reports per env const [envEvents, setEnvEvents] = vUseState({}); // event totals/histogram per env const [selIncident, setSelIncident] = vUseState(null); const [drill, setDrill] = vUseState(() => evParseDrill(initialParams)); const [teams, setTeams] = vUseState([]); const setF = vUseCallback((patch) => setFilters((f) => ({ ...f, ...patch })), []); const range = vUseMemo( () => EV_API.resolveRange(filters.rangeId, filters.customFrom, filters.customTo), [filters.rangeId, filters.customFrom, filters.customTo] ); vUseEffect(() => { EV_API.loadTeams().then(setTeams); }, []); /* ---- hash sync (shareable filter + drill state) ---- */ vUseEffect(() => { EV_API.writeHash('errors', { envs: filters.envs.length === 4 ? '' : filters.envs.join(','), range: filters.rangeId === '7d' ? '' : filters.rangeId, from: filters.rangeId === 'custom' ? filters.customFrom : '', to: filters.rangeId === 'custom' ? filters.customTo : '', feature: filters.feature, step: filters.step, team: filters.team, brand: filters.brand, bk: drill ? [drill.env, drill.backendId, drill.category].filter(Boolean).join(':') : '', }); }, [filters, drill]); /* ---- aggregate reports per env (throttled, cached) ---- */ const loadEnvs = vUseCallback((envs) => { setEnvState((s) => { const next = { ...s }; envs.forEach((env) => { next[env] = { loading: true, error: null, data: (s[env] || {}).data }; }); return next; }); EV_API.loadAllEnvs(envs, range).then((res) => { setEnvState((s) => { const next = { ...s }; envs.forEach((env) => { next[env] = { loading: false, error: res[env].error, data: res[env].data }; }); return next; }); }); }, [range]); vUseEffect(() => { loadEnvs(filters.envs); }, [loadEnvs, filters.envs.join(',')]); /* ---- per-env event totals + histogram for the matrix ---- size:1 — we only need totalCount / histogram / byCategory / coverageSince here; raw rows are fetched on demand by the drill drawer. */ const loadEvents = vUseCallback((envs) => { setEnvEvents((s) => { const next = { ...s }; envs.forEach((env) => { next[env] = { ...(s[env] || {}), loading: true, error: null }; }); return next; }); const opts = { page: 0, size: 1, feature: filters.feature, step: filters.step, team: filters.team, brand: filters.brand }; EV_API.mapLimitSettled(envs, 2, (env) => EV_API.loadRawErrors(env, range, opts)) .then((results) => { setEnvEvents((s) => { const next = { ...s }; results.forEach((r, i) => { const env = envs[i]; if (r.status === 'fulfilled') { const byCategory = {}; r.value.histogram.forEach((b) => { Object.entries(b.byCategory).forEach(([c, n]) => { byCategory[c] = (byCategory[c] || 0) + n; }); }); next[env] = { loading: false, error: null, totalCount: r.value.totalCount, byCategory, histogram: r.value.histogram, bucketMs: r.value.bucketMs, coverageSince: r.value.coverageSince, }; } else { next[env] = { loading: false, error: (r.reason && r.reason.message) || 'Unavailable' }; } }); return next; }); }); }, [filters.feature, filters.step, filters.team, filters.brand, range]); vUseEffect(() => { loadEvents(filters.envs); }, [loadEvents, filters.envs.join(',')]); /* No auto-refresh: this is a forensic view — data changes slowly and a refresh cycle stacked new requests behind hung aggregate calls on the small AppRunner instance. Re-fetch happens on range/filter change or via the per-env Retry button (timed-out fetches drop their cache entry). */ /* ---- derived ---- */ const timelineRows = vUseMemo(() => { const rows = []; filters.envs.forEach((env) => { const st = envState[env]; if (!st || !st.data || !st.data.incidents) return; const byFeat = new Map(); st.data.incidents.incidents.forEach((ic) => { if (filters.feature && ic.feature !== filters.feature) return; if (!byFeat.has(ic.feature)) byFeat.set(ic.feature, []); byFeat.get(ic.feature).push(ic); }); Array.from(byFeat.entries()).forEach(([feature, incidents]) => { rows.push({ key: env + ':' + feature, env, feature, incidents }); }); }); return rows.sort((a, b) => { const ao = a.incidents.some((i) => i.ongoing), bo = b.incidents.some((i) => i.ongoing); if (ao !== bo) return bo - ao; return a.key.localeCompare(b.key); }); }, [envState, filters.envs.join(','), filters.feature]); const ongoingCount = vUseMemo(() => timelineRows.reduce( (n, r) => n + r.incidents.filter((i) => i.ongoing).length, 0), [timelineRows]); /* patterns for the drill drawer: env-scoped topMessages limited to the drilled backend's categories */ const drillPatterns = vUseMemo(() => { if (!drill) return []; const st = envState[drill.env]; if (!st || !st.data || !st.data.failures) return []; const backend = EV_BACKENDS.find((b) => b.id === drill.backendId); const cats = drill.category ? [drill.category] : backend.cats; return (st.data.failures.topMessages || []) .filter((m) => cats.includes(m.category)) .map((m) => ({ env: drill.env, ...m })) .sort((a, b) => b.count - a.count); }, [drill, envState]); const features = vUseMemo(() => { const s = new Set(); Object.values(envState).forEach((st) => { if (st && st.data && st.data.incidents) st.data.incidents.incidents.forEach((i) => s.add(i.feature)); }); if (filters.feature) s.add(filters.feature); return Array.from(s).sort(); }, [envState, filters.feature]); /* coverageSince rides the /errors response (oldest failure row per env) — it lets us say "no data recorded" instead of a misleading "no errors" for windows that predate observability or fell past retention. */ const coverageSince = vUseMemo(() => { let min = null; Object.values(envEvents).forEach((d) => { const c = d && d.coverageSince; if (c && (!min || new Date(c) < new Date(min))) min = c; }); return min; }, [envEvents]); const beforeCoverage = coverageSince && range.from < new Date(coverageSince).getTime(); const totalEvents = filters.envs.reduce((s, env) => { const d = envEvents[env]; return s + ((d && d.totalCount) || 0); }, 0); return (

Environment errors

Creation-pipeline only (iTDM customer / order / PID) — reservation, login and TDS-API errors are not recorded here.
{(filters.step || filters.team || filters.brand) && (
Step / team / brand filters apply to the failing-backends matrix and raw events — incident and pattern aggregates don't carry those dimensions.
)} {(range.clamped || beforeCoverage) && (
{range.clamped && Window clipped to the {EV_API.RETENTION_DAYS}-day retention horizon. } {beforeCoverage && Observability data only exists since {evFmtDay(coverageSince)} — earlier days show no data recorded, which is not the same as no errors.}
)}
{filters.envs.map((env) => ( { loadEnvs([env]); loadEvents([env]); }} /> ))}
0 && ( {ongoingCount} ongoing )} > !envState[env] || envState[env].loading)} /> setSelIncident(null)} /> 0 && {totalEvents} errors in window} > setDrill(d)} />
⚠ Counts reflect replenish attempts — exponential backoff makes broken features burst-then-silent, so trends show attempt cadence, not severity. Category mapping is best-effort; the raw message (in the drill-down) is ground truth.
setDrill(null)} />
); } window.ErrorsView = ErrorsView;