Skip to content

feat(viewer): interactive HTML viewer for JSON reports (#50) - #56

Closed
NikolaosSokos wants to merge 26 commits into
mainfrom
worktree-issue-50-report-viewer
Closed

feat(viewer): interactive HTML viewer for JSON reports (#50)#56
NikolaosSokos wants to merge 26 commits into
mainfrom
worktree-issue-50-report-viewer

Conversation

@NikolaosSokos

Copy link
Copy Markdown
Collaborator

Closes #50.

A dependency-free static viewer that renders EIDA consistency JSON reports as an interactive HTML page — no framework, no build step.

What's included (viewer/)

  • Pure core (viewer.core.js) + 42 node:test cases (viewer.core.test.mjs), DOM glue (viewer.js), and shell (viewer.html).
  • Summary header: SVG score gauge, direction bars, chips.
  • Results table: filter (only-inconsistent / direction / search), click-to-sort headers, gap-count badge, max-gap column.
  • Detail view (auto-scrolls in on row click): graphical two-lane SVG timeline (Availability vs Dataselect, mismatch shaded, hover times) + ASCII timeline; full-window and per-gap Run / open / copy for both services.
  • HAS DATA / NO DATA badge on Run (byte-based indicator).
  • dataselect derivation for older reports lacking dataselect_url.
  • Loading: ?report=<url> (opaque), file picker, drag-and-drop.
  • Landing: index.json manifest → one row per node. Generators make-index.mjs (local dir) and make-oculus-index.mjs (crawl Oculus, latest per node). Manifest is git-ignored/generated.
  • Markdown reports gain a 🔍 Interactive view link (report.py), with tests.

Validation

  • Viewer core: 42/42 node --test pass.
  • Python suite: 172 passed (the report-link change is covered).
  • Smoke-rendered the real 30-result sample-report.json through the core (summary + table + detail) with no errors.
  • Branch is up-to-date with main, 23 commits ahead — clean merge.

Deployment note (follow-up, not in this PR)

The markdown link uses VIEWER_BASE = "../../viewer.html" (relative). The Oculus hosting path / whether Oculus publishes index.json is deferred — override VIEWER_BASE when the serving location is decided.

Replace the colored-bar timeline with the ASCII glyph timeline
(█ both / · neither / ▲ dataselect-only / ▼ availability-only / | gap
boundary) to match the CLI/Markdown output. Refresh the CSS (sticky
toolbar, monospace timeline, card detail, row hover/selection, styled
request buttons/links). Swap the misleading hand-made fixture for a real
30-epoch NOA report (7 inconsistent rows, full coverage+mismatch).
…rowser

- Summary: SVG score gauge (color by threshold) + direction breakdown bars
  alongside the consistent/inconsistent/skipped chips.
- Results table: click-to-sort column headers (toggle asc/desc), a gap-count
  badge, and a Max-gap column; sort dropdown removed.
- Detail: per-gap duration, copy-to-clipboard for every request URL, and a
  clearer full-window vs per-gap grouping.
- Report browser: landing page renders an index.json manifest of available
  reports; a URL box loads any report (e.g. an Oculus report.json) without
  hand-editing the query string. Demo manifest lists NOA + RESIF samples.

31 node:test cases pass (added fmtDuration, gapStats, renderIndex, table
badge/sort-header, per-gap duration + copy).
The landing must list every available report, not a hand-picked pair.
Add make-index.mjs: scans a report directory and writes index.json with
one entry per report (name, url, node, score, timestamp, inconsistent),
newest first. The manifest and the reports symlink are generated/local
artifacts, so untrack the curated index.json + the redundant resif copy
and gitignore them; deployments (Oculus) regenerate the manifest over
their own report tree.
Keep the landing a manifest-driven slot: if an index.json is present
(e.g. published by Oculus alongside its latest report JSONs) the list is
shown; otherwise the loader stands alone. No scraping or hardcoded report
lists. Loading now works three ways — paste a report .json URL, choose a
local file, or drop a file onto the page — with validation that the file
is an EIDA consistency report before rendering.
Add a two-lane SVG coverage chart (Availability lane + Dataselect lane,
mismatch regions highlighted, hover tooltips with exact times) shown above
the ASCII line when a record has coverage. For older reports without
coverage (e.g. those currently on Oculus) fall back to a single-track
'request window with gaps' chart so every inconsistency still gets a
graph. Scales to container width; colours via CSS vars for dark mode.
Add make-oculus-index.mjs: crawls the public Oculus consistency tree, picks
the latest report per node, reads each summary, and writes index.json with
absolute year-level JSON URLs (Oculus serves these with open CORS, so the
browser loads any entry cross-origin). The landing renders the manifest as
a one-row-per-node dashboard with node, score (colour-coded), timestamp,
and inconsistent count; the URL/file loader stays available beneath it.
index.json is a generated artifact (gitignored) — re-run the crawler to
refresh, or point the viewer at an Oculus-published manifest.
Older reports (e.g. those currently on Oculus) store only the availability
url. Derive the dataselect URL from it — swap the service path and the
start/end param names, narrow location/channel to the record's stream — so
the detail view offers Run/open/copy for dataselect too, both full-window
and per-gap. Stored dataselect_url still wins when present.
runRequest now returns hasData (availability spans > 0, or dataselect 200
with bytes). The detail view shows a colour-coded pill next to each Run
button — green HAS DATA, red NO DATA, grey FAILED — with the HTTP/size
summary beside it. Re-running a request replaces its previous result
instead of stacking.
Clicking a row now smooth-scrolls the detail section into view so the
timeline and Requests are immediately visible. Add viewer/README.md
documenting the architecture, data flow, every module/function, the
timeline renderers, request replay, loaders, security, manifest
generators, and how Oculus integrates.
The design write-up was meant as personal documentation, not a product
artifact served by the viewer. Remove the in-page 'How this viewer works'
link and move the doc out of the served viewer/ folder.
The table showed a bare ▲/▼ glyph for direction and the dataselect HTTP
status (often 'OK') for an inconsistent row, so what the inconsistency was
never read in plain language.

- Results table: 'Dir' -> 'Disagreement' column now shows a labelled pill
  ('▲ Data only' / '▼ Avail only', full phrasing on hover); 'Status' -> 'Result'
  column shows the verdict in words (Inconsistent / Consistent / Skipped)
  instead of the raw dataselect status.
- Detail view now leads with a plain sentence, e.g. 'Inconsistency: for 10m
  (…), dataselect returned data but availability reported none.'
- New core helpers recordVerdict() and explainRecord() (+5 tests, 47 total).
Every row's max gap equals the fixed 10-minute request window, so the column
(and sorting by it) carried no information. Remove it; the 'Gaps' column now
sorts by gap count.
Filter options now read 'all ▲▼ / ▲ Data only / ▼ Avail only', matching the
column pills. Behaviour unchanged (still filters by gap direction).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Render HTML pages directly from the json report

1 participant