Skip to content

fix(history): resolve jobs outside the loaded page - #1922

Open
arlophoenix wants to merge 3 commits into
fluidd-core:developfrom
arlophoenix:fix/file-browser-history-window
Open

fix(history): resolve jobs outside the loaded page#1922
arlophoenix wants to merge 3 commits into
fluidd-core:developfrom
arlophoenix:fix/file-browser-history-window

Conversation

@arlophoenix

Copy link
Copy Markdown

Refs #998

The G-code file browser shows a Status and Total Duration per file, both resolved from the history job the file's metadata names. Only the newest JOB_HISTORY_LOAD (50) jobs are ever loaded, so a file whose last print is older than that page renders blank — indistinguishable from a file that has never been printed. Opening History → Load All fixes it until the next reconnect, which re-runs history/init and drops back to 50.

Raising the constant only moves the cliff. The browser needs the job behind each file's last print, and those are spread across the whole history rather than clustered at the recent end, which is the one thing a "newest N" page guarantees. On a printer with 127 jobs and 75 linked files, a page of 50 renders 33 and blanks 42; a page of 100 renders 73 and blanks 2; only "all" renders every one — and "all" is exactly what a default must not be.

So instead of widening the page, this resolves the misses individually. A directory load collects the job ids its files name, drops the ones already loaded, already resolved, already known missing, or already in flight, and fetches the rest through server.history.get_job — two requests in flight at a time, because Moonraker serializes every history endpoint behind one lock, so more concurrency buys no wall-clock and a wide pool would hold that lock against the History page. Results are committed once per drain and kept beside the loaded page rather than merged into it, so History keeps showing a contiguous page. The same trigger runs for a single file's metadata, because files/getFile is decorated the same way and has eight consumers.

Deleting a job leaves its job_id on the file — Moonraker's delete_job only touches job_history — so orphaned ids are easy to produce, and Fluidd's own Remove All produces them for every file at once. Those come back 404. Each one is recorded so it is asked for exactly once, and the 404 is suppressed rather than toasted, via a new per-request suppressError on NotifyOptions. It is opt-in per request: every other 4xx in the app keeps its toast, which is what the first test below guards. The negative cache is dropped on reconnect, since a 404 recorded against a previous connection is not evidence about this one, and cleared for an id when a job is added under it — Moonraker's job ids are a SQLite INTEGER PRIMARY KEY with no AUTOINCREMENT, so they are reused.

Two measurements, one printer each, both read from Moonraker's own API rather than off a rendered page. A stock Creality K1C with no local patches: 184 history jobs, 112 G-code files, 105 carrying a job_id, of which 64 fall outside the 50-row page. All 64 were probed with server.history.get_job and all 64 resolve — zero orphans — so every one is a completed print still in the database that the browser renders as blank. That is 61% of the files that have ever printed, or 57% of all G-code files. The 127-job figures above come from a second printer.

An alternative was considered and rejected on measurement: one server.history.list scoped by since: min(print_start_time) over the misses, which would have been a single round trip and no 404 path at all. Against the stock printer that query returns 183 of 184 rows — 99% of the history — because the oldest miss sits at the very start of it. It collapses into limit: 0.

While in getHistoryById, it now reads from an index rather than a linear scan. It is a method-style getter called once per file by files/getDirectory, which made decorating a directory O(files × jobs).

Test plan

  • pnpm run lint clean
  • pnpm run type-check clean
  • pnpm run test:unit — 447 tests pass, including 47 new ones covering the cache, its invalidation, the bounded drain, the reconnect repair, and files/getDirectory returning a populated history for a file outside the loaded page
  • pnpm run circular-check clean
  • Manual test pass (see checklist below) — not performed; no printer was available to the session that opened this PR

Manual test pass

  • On a printer with more than 50 history jobs, open Jobs. A file whose last print is older than the 50th-newest job shows a real Status (e.g. Completed) and a real Total Duration (e.g. 1h 12m) without visiting History → Load All.
  • History → Remove All, then open Jobs. Zero red toasts; every Status and Total Duration is blank.
  • Delete one job in History whose file is visible in Jobs. That file's Status and Total Duration go blank immediately.
  • Trigger any other Moonraker 4xx (e.g. delete a file that no longer exists). Its usual red toast still appears.
  • Restart Moonraker while on Jobs. After the socket reconnects, the columns are still populated.

Not addressed

JSON-RPC errors are always dispatched to socket/onSocketError, which
toasts every 4xx. A caller that expects and handles a particular error
had no way to opt out of that.

Add NotifyOptions.suppressError, honoured per request. The promise
still rejects, so the caller keeps handling it; only the global toast
is skipped.

Signed-off-by: arlophoenix <arlo.phoenix@gmail.com>
The file browser reads a file's status and total duration from the
history job its metadata names, but only the newest JOB_HISTORY_LOAD
jobs are ever loaded. A file whose last print is older than that page
renders blank, indistinguishable from one that never printed.

Resolve those jobs individually with server.history.get_job, triggered
from the two actions that write file metadata, and cache them beside
the loaded page. Orphaned ids - Moonraker never clears job_id from a
file when the job is deleted - are recorded so they are asked for once,
and their 404 is suppressed rather than toasted.

Also index the history by id so decorating a directory stops being
O(files x jobs).

Signed-off-by: arlophoenix <arlo.phoenix@gmail.com>
First store tests in the repo, so the vitest project now compiles app
code it never reached before: it needs the WebWorker lib src/sw.ts
supplies to the app project, and Vue's $filters / $colorset, which are
declared inside plugins that drag in the router.

Two of these are regressions guards rather than coverage: that an
unsuppressed JSON-RPC error still reaches socket/onSocketError, and
that deleting a job stops the browser resolving it.

Signed-off-by: arlophoenix <arlo.phoenix@gmail.com>
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.

1 participant