fix(history): resolve jobs outside the loaded page - #1922
Open
arlophoenix wants to merge 3 commits into
Open
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #998
The G-code file browser shows a
StatusandTotal Durationper file, both resolved from the history job the file's metadata names. Only the newestJOB_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-runshistory/initand 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, becausefiles/getFileis decorated the same way and has eight consumers.Deleting a job leaves its
job_idon the file — Moonraker'sdelete_jobonly touchesjob_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-requestsuppressErroronNotifyOptions. 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 SQLiteINTEGER PRIMARY KEYwith noAUTOINCREMENT, 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 withserver.history.get_joband 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.listscoped bysince: 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 intolimit: 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 byfiles/getDirectory, which made decorating a directory O(files × jobs).Test plan
pnpm run lintcleanpnpm run type-checkcleanpnpm run test:unit— 447 tests pass, including 47 new ones covering the cache, its invalidation, the bounded drain, the reconnect repair, andfiles/getDirectoryreturning a populatedhistoryfor a file outside the loaded pagepnpm run circular-checkcleanManual test pass
Completed) and a real Total Duration (e.g.1h 12m) without visiting History → Load All.Not addressed
getHistoryByFilenamehas no consumers and carries the same window defect. Left alone to keep this single-concern.state.countis the number of rows returned, not the number of rows that exist, and is never decremented on delete. Nothing reads it today, and no "is everything loaded" signal can be built from it. Filed separately as history store's state.count is rows-returned, not the total #1920.