Repository navigation
🐛 Stop the refresh button from jumping when a query is in progress - #3117
Closed
johnsimons wants to merge 19 commits into
Closed
johnsimons wants to merge 19 commits into
johnsimons wants to merge 19 commits into
Conversation
The very-long query on first view access was not the message list (which is bounded by the default time range) but the hasSuccessfulMessages capability probe: an UNBOUNDED sorted query over the whole audit index, re-fired every 5 seconds by the platform-capabilities poll. It now probes the last 7 days first, falls back to unbounded only when that window is empty, and stops querying entirely once successful messages have been seen — they don't un-exist.
The 'no successful audit messages' onboarding banner and dashboard card assumed 'no messages' by default (the flag started false) and un-rendered once the probe proved otherwise — on large stores that meant minutes of a wrong onboarding prompt, and on every fresh session a flicker. hasSuccessfulMessages is now tri-state: null (not yet determined), false (a completed probe found nothing), true. While unknown the auditing capability reports a new Checking status: the All Messages banner renders nothing and the dashboard card shows its loading overlay. A failed probe proves nothing about the data, so it leaves the state unknown and the next tick retries instead of claiming 'no messages'.
Show (page size) and Sort are result-presentation controls, not query filters — they now sit right of 'Showing X of Y result(s)' instead of competing with the query inputs for space in the filters panel, which also removes their awkward wrapping. The filters panel keeps the query inputs only (search, endpoint, time range), left-aligned; the rule that pushed the last filter to the far right is gone.
Large audit stores easily reach nine-digit totals; 'Showing 100 of 158736340 result(s)' is unreadable. Both numbers now go through Intl.NumberFormat with the user's locale (158,736,340 / 158.736.340). ResultsCount is shared, so the heartbeats views and the throughput queue list pick this up as well.
The refresh cluster occupied its own row above the filters; it now sits right-aligned inside the query bar so the whole query surface is one row. While a query is in flight the button flips from Refresh to an enabled Cancel that ticks the elapsed time (Cancel · 3.2s) and aborts the running query through the store — the abort propagates through ServiceControl and terminates the database-side query. Once results land, the results line reports what they cost: 'Showing 100 of 158,736,340 result(s) · took 2.7 s'. ActionButton hard-coupled loading to disabled, which would have left the button dead exactly when a user most wants an escape hatch; it gains disableOnLoading (default true, existing uses unchanged) and the refresh/cancel button opts out. The spec stubs ActionButton with the real disabled semantics so that coupling can't silently return.
While auto-refresh is armed and no query runs, the refresh arrow is replaced by a small ring that depletes toward the next refresh, with the exact remaining seconds as its tooltip; during a query the button shows the usual spinner and Cancel. The ring mirrors FontAwesome's icon geometry — ActionButton gains a named icon slot so custom icons are flex children with the same centering as FAIcon, and the ring's circle fills its box like a glyph — measured pixel-identical to the arrow state in both axes. Reduced motion disables the animation.
The countdown ring inside the refresh button was an SVG carrying a label that changed every second, which became part of the button's accessible name. The ring is now purely decorative (aria-hidden) and the countdown is a visually hidden "Next auto refresh in N seconds" timer that describes the button: read together with it, not announced on every tick. The button keeps its stable name "Refresh".
Leaving the view clears the rows; the "took X s" that described them must not survive to the next visit.
Any query that used search text or an endpoint is remembered in this browser (most recent first, capped, no duplicates) behind a History control, with the time-range expressions it ran with: the range is part of a search's identity, entries show it, and rerunning restores it so a history entry is a true "run this exact query again".
Review feedback: a separate "History" dropdown sat next to the search field yet listed whole queries, which felt disconnected. The history now wraps the search field and floats below it when the field gets focus, the way a browser address bar does. Typing narrows the list to entries whose search text or endpoint match; Escape dismisses it and a click in the field brings it back. Each entry still carries the full query (text, endpoint, time range) and reruns all of it. An empty history shows nothing.
Time Sent adapts to age — time only for today, 'yesterday 14:32' and weekday names for the past week, the regular browser format beyond — with a live coarse relative label (moments/minutes/hours/days/weeks/ months/years ago) and the full timestamp in both zones as tooltip. A 'Times: local|UTC' toggle on the results line flips every timestamp to UTC wall-clock time (day boundaries included), persisted per browser. The results line also shows when the current results were fetched: 'ran 3 minutes ago', with the exact timestamp as tooltip.
The audit row's grid gave every column an equal share, so short metric cells sat on unused whitespace while a long Time Sent value wrapped mid-label — and because every row was its own grid, a row with 'yesterday 8:22:30 PM' laid out differently from its neighbours. The columns are now defined once on the list and shared by every row via subgrid: the message type owns the top line; the id, the three metrics and the sent time share the bottom line, bottom aligned. Data columns never shrink below the widest value in the list (nothing wraps) but share surplus width equally, so they stretch across the row on wide screens. The age sits in its own content-sized column, right aligned so every 'ago' ends at the same edge, and is hidden below 62rem — the absolute timestamp carries the information. AdaptiveTimestamp gained a part prop so the absolute time and the age can occupy separate grid cells while both keep the dual-zone tooltip.
Leaving the view clears the rows; "ran X ago" described those rows and must not show for an empty list on the next visit.
Under auto-refresh new messages simply appeared at the top of the list with nothing to tell them apart from the rows already there. No results view in ServicePulse had an arrival animation to reuse (the saga diagram's blink-border is the only one-shot animation and targets diagram nodes), so this adds one for All Messages. The store now records which ids of a result were absent from the previous result of the same query. The list marks those rows and a one-shot CSS animation slides them in and lets a blue tint fade over three seconds; the element is new to the DOM (rows are keyed by id), so it plays once on insertion. Nothing animates on the first result or when the query itself changed: then every row is different and highlighting all of them says nothing. A failed query resets the baseline, so the recovery result does not light up either. Reduced motion keeps the tint but drops the movement.
After the view is left and re-entered the first result is a fresh start, not "everything is new compared to the empty list".
ServiceControl (Particular/ServiceControl#5848) now answers a partial composite with the X-Particular-Incomplete-Results header naming every instance whose data is missing (instanceId:timeout|unavailable|error), and a fully timed-out query with a 504. All Messages now reads both: a partial page keeps its data but shows a 'Partial results' warning naming each missing instance and its reason, and a 504 gets a precise 'exceeded the ServiceControl query time limit' message instead of the generic failure text. The header parser is shared so the conversation and saga views can adopt it next.
ServiceControl identifies an audit instance by its API URL, lower-cased
and base64 encoded with the URL-safe alphabet ('-', '_' and '.' for
'+', '/' and '='), and that is what the X-Particular-Incomplete-Results
header carries. The partial-results warning printed the id as-is, so a
reader saw "No data from aHR0cDovL2xvY2FsaG9zdDo0NDQ0NC9hcGkv (timed out)".
The id is now decoded and shown as host and port (default port
omitted), with the full API URL as tooltip: "No data from
localhost:44444 (timed out)". An id that is not a base64 http(s) URL is
shown unchanged, so nothing depends on the encoding staying that way.
Leaving the view clears the rows; the partial-results warning and the time-limit flag described those rows.
The elapsed-time label ("Cancel · 1.2s") updated every 100 ms, causing the button to resize continuously while a query ran. Removing the label and fixing the button width keeps it stable.
ramonsmits
force-pushed
the
ramon/audit-incomplete-results
branch
7 times, most recently
from
September 9, 2026 07:28
2a9c6b2 to
f06e120
Compare
ramonsmits
force-pushed
the
ramon/audit-incomplete-results
branch
from
September 9, 2026 07:43
f06e120 to
8472b45
Compare
ramonsmits
force-pushed
the
ramon/audit-incomplete-results
branch
4 times, most recently
from
September 9, 2026 10:19
14d4134 to
27899a2
Compare
Member
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.
Reviewer Checklist